When you need to escape an abstraction, how violent is your exit?
The Web is a hugely complicated system. If you’re writing a typical web app, your data will be in about 6 different forms:
- Rows in an SQL table
- Objects in your server code (eg Python)
- JSON payloads on HTTP endpoints
- HTML DOM elements
- Text on the screen
Translating these layers is tedious and repetitive, so we’ve invented a ton of frameworks to help us:
- ORMs translate SQL rows into objects
- REST frameworks turn objects into HTTP endpoints
- …and so on.
Each of these frameworks is a new layer of abstraction.
Every problem in computer science can be solved by another layer of indirection…
– David Wheeler
…except for the problem of too many layers of indirection.
All abstractions “leak”, requiring you to understand what’s happening underneath. Web frameworks leak all the time – you can’t effectively use Angular or React, for example, without understanding the DOM.
But today we’re asking about a worse situation – what happens when your abstraction just can’t do what you need?
Option 1: Go down with the ship
Let’s say you’ve written your app as a Windows desktop application, and now you need to use it on the web. Too bad – you’re going down with the ship. Your only choices are to rewrite your app on a new platform, or sit tight and hope that feature isn’t so critical after all.
Option 2: The Ejector Seat
If you’re lucky, your abstraction is more helpful than that.
Of course, sooner or later you’ll hit the limits of what create-react-app can do: you’ll need something it doesn’t support. The authors have thought of this situation, and there’s a button you can push:
Ejecting from create-react-app opens up the black box. It writes out a set of build scripts representing the current state of your app. Now you can edit those scripts yourself – the hard way.
But create-react-app has given up. It protected you for a while – but as soon as you needed one advanced feature, you’ve been dropped in the Mojave desert with a parachute, a first-aid kit, and a copy of the Webpack manual.
It’s better than going down with the ship, of course. But should you have to discard an abstraction entirely when you touch its limits?
Option 3: The Escape Hatch
It’s much more helpful to acknowledge that your system will always be incomplete, and make it usable even then. Ideally, your users can use complex external tools for the tricky corners of their project, without making the other 95% of their work more complicated. I call this pattern an “escape hatch”, rather than an “ejector seat” – you don’t need to jump all the way out at once.
The Rust programming language is a great example. Rust is a memory-safe low-level language: The compiler enforces rules that make sure your code can’t access memory it shouldn’t. But there are some things you can’t express within those rules. So Rust has an escape hatch, called
unsafe. You mark a block of code
unsafe, and you can break those rules. A few lines later, you return to writing the rest of your app – with compiler-guaranteed memory safety.
Anvil is an abstraction over all of web development. We provide a visual UI designer (no HTML/CSS), and you write your front-end and back-end code in Python (no JS, no REST frameworks). It’s even got an optional Python-native database (no SQL). This enables a data scientist, a sysadmin, or a developer in a hurry to build full-stack web apps with nothing but Python.
This is a pretty ambitious goal: The Web is a huge platform, with a large set of existing libraries, and the browser vendors add new features all the time. Let’s face it: Our new abstraction isn’t going to cover it all, any time soon.
We could have provided an ejector seat: “Export this Anvil app as a React+Flask app”. This, bluntly, would suck. The moment you edited the generated code, you’d lose all the benefits of Anvil’s abstractions!
So instead, we built escape hatches into every part of Anvil. For example:
If you want finer control over appearance, you can leave the drag-and-drop designer and edit the HTML template it uses. And then… flip right back to the drag-and-drop designer, where you can drop components into your new page layout.
If you want to run code outside our serverless environment, it’s easy. Just install our Uplink library on your server, and run your code there. And then… call that code from your app, exactly the same as calling serverless code. Your server code can use all of Anvil’s server-side APIs. (Here’s an example with a Raspberry Pi.) Of course, if you need everything on site, you can get an on-site installation.
If you want to edit your code outside Anvil, just check it out in Git! Every Anvil app is a Git repository. Pull, make some changes, push it back. You can automate this, and integrate with whatever code review or CI/CD system you like. And then… jump right back into Anvil, and edit your app there.
The web is a complex platform – often too complex – but every single feature is there for a reason.
Are you building an abstraction? If so, think about your escape hatches: How can your users exploit features of the underlying platform, without ejecting into the wild blue yonder?