Innovation frontierfive days beforeSo we decided to innovate. Great! Where do we begin? How do…
Porcelains
How to make a breakthrough2022The title is a bit tongue-in-cheek, because I am not actually providing…
My friend Dion asked me to write this down. It’s a neat little pattern that I just recently uncovered, and it’s been delighting me for the last couple of days. I named it “porcelains”, partially as an homage to spiritually similar git porcelains, partially because I just love the darned word. Porcelains! ✨ So sparkly.
The Layering Principle2023Preference toward layering is probably one of the more fundamental principles of…
The Wallpapering Principle2023This principle builds on the layering principle, and deals with a common…
Developer surface2021I mentioned this concept before, and I feel like it’s worth expanding…
The pattern goes like this. When we build our own cool thing on top of an existing developer surface, we nearly always do the wrapping thing: we take the layer that we’re building on top and wrap our code around it. In doing so, we immediately create another, higher layer. Now, the consumers of our thing are one layer up from the layer from which we started. This wrapping move is very intuitive and something that I used to do without thinking.
// my API which wraps over the underlying layer.
const callMyCoolService = async (payload) => {
const myCoolServiceUrl = "example.com/mycoolservice";
return await // the underlying layer that I wrap: `fetch`
(
await fetch(url, {
method: "POST",
body: JSON.stringify(payload),
})
).json();
};
// ...
// at the consuming call site:
const result = await callMyCoolService({ foo: "bar" });
console.log(result);
Four layers2023An illustration for how this plays out is the abundance of the…
The cost of opinion2022It appears that I’ve been writing this essay since 2012, never quite…
Layer gaps2023I’ve been writing a bit more code lately, and so you’ll notice…
Cheesecake and Baklava2022I have been reading Alex Komoroske’s Compendium cards on platforms, and there’s…
However, as a result of creating this layer, I now become responsible for a bunch of things. First, I need to ensure that the layer doesn’t have too much opinion and doesn’t accrue its cost for developers. Second, I need to ensure that the layer doesn’t have gaps. Third, I need to carefully navigate the cheesecake or baklava tension and be cognizant of the layer thickness. All of a sudden, I am burdened with all of the concerns of the layer maintainer.
Declarative vs. imperative2024Another technique is something that Bernhard Seefeld is actively experimenting with: intentionally…
Asymptotes and Value Niches2023I have been thinking lately about a framing that would help clarify…
It’s alright if that’s what I am setting out to do. But if I just want to add some utility to an existing layer, this feels like way too much. How might we lower this burden?
This is where porcelains come in. The porcelain pattern refers to only adding code to supplement the lower layer functionality, rather than wrapping it in a new layer. It’s kind of like – instead of adding new plumbing, put a purpose-designed porcelain fixture next to it.
Consider the code snippet above. The fetch API is pretty comprehensive and – let’s admit it – elegantly designed API. It comes with all kinds of bells and whistles, from signaling to streaming support. So why wrap it?
What if instead, we write our code like this:
// my API which only supplies a well-formatted Request.
const myCoolServiceRequest = (payload) =>
Request("example.com/mycoolservice", {
method: "POST",
body: JSON.stringify(payload),
});
// ...
// at the consuming call site:
const result = await (
await fetch(myCoolServiceRequest({ foo: "bar" }))
).json();
console.log(result);
Sure, the call site is a bit more verbose, but check this out: we are now very clear what underlying API is being used and how. There is no doubt that fetch is being used. And our linter will tell us if we’re using it improperly.
We have more flexibility in how the results of the API could be consumed. For example, if I don’t actually want to parse the text of the API (like, if I just want to turn around and send it along to another endpoint), I don’t have to re-parse it.
Instead of adding a new layer of plumbing, we just installed a porcelain that makes it more shiny for a particular use case.
Because they don’t call into the lower layer, porcelains are a lot more testable. The snippet above is very easy to interrogate for validity, without having to mock/fake the server endpoint. And we know that fetch will do its job well (we’re all in big trouble otherwise).
There’s also a really fun mix-and-match quality to porcelain. For instance, if I want to add support for streaming responses to my service, I don’t need to create a separate endpoint or have tortured optional arguments. I just roll out a different porcelain:
// Same porcelain as above.
const myCoolServiceRequest = (payload) =>
Request("example.com/mycoolservice", {
method: "POST",
body: JSON.stringify(payload),
});
// New streaming porcelain.
class MyServiceStreamer {
writable;
readable;
// TODO: Implement this porcelain.
}
// ...
// at the consuming call site:
const result = await fetch(
myCoolServiceRequest({ foo: "bar", streaming: true })
).body.pipeThrough(new MyServiceStreamer());
for await (const chunk of result) {
process.stdout.write(chunk);
}
process.stdout.write("\n");
I am using all of the standard Fetch API plumbing – except with my shiny porcelains, they are now specialized to my needs.
The biggest con of the porcelain pattern is that the plumbing is now exposed: all the bits that we typically tuck so neatly under succinct and elegant API call signatures are kind of hanging out.
Shadows and Curses2022The second curse can be seen as the interplay between “ 🔮…
Polarity-based API layering2021While discussing API design, one pattern that caught my eye was polarity-based…
Reasoning boxesthree days afterThis story begins with the introduction of metacognition to large language models…
This might put some API designers off. I completely understand. I’ve been of the same persuasion for a while. It’s just that I’ve seen the users of my simple APIs spend a bunch of time prying those beautiful covers and tiles open just to get to do something I didn’t expect them to do. So maybe exposed plumbing is a feature, not a bug?
Show 3 comments
DI-Container · May 16, 2023
It’s called dependency injection
liupangzi · May 16, 2023
Lol
Arjun · May 28, 2023
Awesome