“Worse is Better” on AWS
If you haven’t read Worse is Better, you should read it now.
You can find all the details in article above. Just to summarize it even more, there are two design styles:
- A simpler interface. Hide the complexity on the server side, but easier for users to understand.
- A simpler implementation. It’s easier for the project developers to maintain and understand, but users might have to know more about the assumptions that the service makes and write more code to handle the complexity that service exposes in the interface.
There is a whole spectrum of trading between simpler interface and simpler implementation. The leftmost side of this spectrum indicates a system with simplest interface and has all complexity baked into it. The rightmost side of this spectrum indicates a system with simplest implementation and has all complexity exposed in the interface. Most of the systems are somewhere in-between; they have some complexity baked in their system, but meanwhile shift other complexity to the interface. Most of the core AWS services are in this category (more on that later).
Here are some examples for both design styles.
Example: Simpler Interface
Services that are open to everyone on the earth fall into this category. Just to name a few:
- Dropbox: as an UI user, you simply upload your files to your Dropbox account. When it says it’s done, it’s done. You don’t need to worry about retry strategy, consistency, latency, etc. If the upload failed, you might want to try it again. However, if you have a Dropbox client, things get even more simpler. You simply put your files in your local folder that is synced with Dropbox and you’re done.
- Amazon one-click purchase: it’s so simple that all you need to do is to click “1-click purchase” button.
- A bowl of ramen: you just have to sit down and eat it. You shouldn’t have to worry about how the chef made this delicious ramen.
Example: Simpler Implementation
A necessary but not sufficient condition for this kind of services is that the targeted users are not everyone but a specific population.
- S3: from an API user’s perspective, you need to be aware that S3 is an eventually consistent system for upload and deletion operations. You might have to write extra code in your system to deal with this assumption.
- Most of the UNIX tool: you use
lsto list a directory,
cdto change a directory,
grepto filter content, etc. In fact, one of the UNIX design philosophy is
do one thing and do it well. However, in lots of cases, you need to use
pipeto glue those tools together.
- An IKEA furniture: you need to do assemble an IKEA furniture.
More About AWS
AWS’s main users are developers. So they are lucky to have an option to build systems with simpler implementation.
I talked about S3. Let’s talk about another two popular AWS services:
- DynamoDB: DynamoDB is entirely an eventually consistent system before they released
Strongly Consistent Readsfeature. You put your DynamoDB item in the table, you might not see that item when you query it immediately.
- SQS: before releasing SQS
FIFOqueue, SQS is entirely an eventually consistent system. This is actually very counter-intuitive for users who don't know much about distributed system: it says that it is a queue but cannot guarantee the delivery ordering and can only deliver messages in
at least oncefashion.
There are couple of compelling reasons for AWS to build systems like this:
- Not all users need strong consistency.
- It’s easier to implement, Building distributed system with strong consistency is difficult (think about PAXOS and Raft) and usually those systems have higher latency and lower throughput.
- If a system is easier to implement, it’s usually easier to test, faster to release. For AWS, releasing new services and new features is very important for acquisition.
As a software engineer, I like Simpler Implementation when building a software system but hate it in other areas. For example, I don’t like barbecue because it requires me to manually put the meat on the fire and you have to PAY to do that! I also don’t like IKEA because I am lazy.
Here are the reasons why I like Simpler Implementation when building a software system:
- To build a simple implementation is actually harder than it sounds. What I observed that junior software engineers usually tend to write complex code and design complex solutions. Senior software engineers tend to come up with simpler implementations. Also, you really need to talk to customers and make sure you actually are able to shift some complexity from your implementation to your interface. Design a truly simple system requires years of experience.
- As I mentioned above, simpler implementation is faster to build. Constantly releasing new features makes everyone happy.
- Operation. Simpler implementation is easy to understand, so it’s easier to maintain and people can quickly know how to resolve the operational issues.
Disadvantages of Simpler Implementation and How to Fix it
Someone said that
if you released a product without any embarrassment, you probably have waited too long. Agreed. Releasing a service with Simpler Implementation design style is to make sure that you can release it faster than the one with Simpler Interface approach, but with some embarrassment. The real question is: after you release your product with those embarrassment, what can you do to improve it?
SQS recently released a new feature called
FIFO queue. This is because they realized that people usually don't read documentation and even they do, traditional SQS queue's design assumptions put too much burdens on users. To use SQS queue, you need to think really hard about your architecture and make sure that the operations incurred after receiving a SQS message have to be idempotent. But sometimes your operations cannot simply be idempotent. One solution could be to put a buffer after the SQS queue so this buffer can remember what message it has received so far. Some people simply believe that those assumptions are unacceptable and use Kafka instead. Releasing
FIFO queue feature makes perfect sense for SQS to fill this gap.
To deal with systems that have not-so-simple interface, lots of users use facade patterns by writing code to hide the complexity. For example, SQS’s FIFO queue requires that every message has an unique ID. In most of cases, an unique ID can be obtained by performing SHA256 on the SQS message itself. Rather than asking every AWS SQS users to write the same code, AWS already added that code in the AWS Client package. A less interesting but more common problem is that everyone wants to retry their requests, so AWS Client natively supports exponential backoff in all the requests to AWS services unless configured otherwise.
UI is actually very important even for software engineers! I don’t completely agree with people who think that terminal is so powerful and they don’t need an UI. Who wants to write lots of code if they can do the same in the browser? Who wants to read the API documentation every time they want to onboard a new service if they can do the same thing with an UI?
When designing a new feature or a new system, I like trying
Simpler Implementation design style first. It's possible that your users may not like the idea of taking on the burdens of handling complexity in your service interface. But even this is the case, you can still use
Simpler Implementation with some stitches later:
- Because you want to release fast, you can always make your system extensible and add new features later (assuming your first release can meet the requirements of most of users).
- Give your users a nice client packages. Complexity in the interface is still there. But you can handle it for your users on their behalf.
- Build an UI to completely hide the service interface complexity.