We offer you a translation of the post of Gergel Oros, who holds the position of Engineering Manager at Uber. In it, he shares his view on the design of large-scale systems, based on his own practical experience in Uber and Microsoft. Combined with comments on Hacker News , which add weighty counter-arguments and complement the author’s point of view, his article has become one of the most interesting posts of the week. The term “code design” is used in the article to compare with traditional “architecture” - you can read more about it here .
I have had enough experience in designing and creating large-scale systems. I took part in rewriting the
distributed payment system at Uber, designing and releasing Skype on the Xbox One, and releasing the publicly available
RIBs , a mobile architecture framework created by Uber. All these systems had a carefully thought-out design, went through several iterations, and many meetings were held at the whiteboard, and other discussions. Then the design came up with a design document, which was distributed among other developers to collect additional feedback, which continued until we moved on to development.
All of these systems were large-scale: hundreds of developers created them — or they used them in their designs — and today they beat in the hearts of systems that millions of people use every day. Moreover, these projects were not created from scratch. The payment system was supposed to replace two other existing payment systems used by dozens of other systems and dozens of teams, all without any harm to the business. Rewriting the Uber application was a project that several hundreds of engineers worked on simultaneously - it included porting all existing functionality to the new architecture.
Let me start with something that may sound unexpected.
First, we never used standard software architecture planning tools to work on the designs of these systems. We used neither
UML , nor the
4 + 1 model, nor
ADR , nor
C4 , nor
the dependency diagrams . We drew many diagrams, but none of them followed any strict rules. All that was needed was the good old rectangles and arrows - and we got diagrams similar to this one
describing information flows , or another,
depicting the structure of the class and the relationship between components . Two charts in the same design document often had a different style, as they were often added or edited by different people.
Secondly, we did not have architects who would own the design. There was neither
IT Architect nor
Enterprise Architect . It’s true - neither Uber, nor Skype / Mircrosoft has a full-time position for software architects. Higher-level engineers, such as those in the Staff Engineer position, are still expected to write code regularly. All our projects have experienced engineers - however, not a single person owns architecture or design alone. While experienced engineers were the driving force behind the design process, the rest of the developers were involved, including even the green "June" - and they often challenged the decisions of others and brought up alternative options for discussion.
Thirdly, we almost never referred to common architectural patterns and other generally accepted jargon, which is used in popular literature on software architecture like
Martin Fowler’s manual . There were no references to microservices, serverless architecture,
application boundaries ,
event-driven architecture, or anything else. Some of these terms surfaced during brainstorming sessions; however, we had no need to refer to them ourselves in the design documentation.
Software Design at Technology Companies and Startups
So how did we deal with everything? And why didn’t we follow common approaches to software architecture?
I discussed this issue with fellow engineers working in other technology companies - both FANG sizes (Facebook, Amazon, Netflix, Google), and small startups. Most teams and projects — whether they were big or small — combined a similar approach to design and implementation:
- Start with a business task. What problem are we trying to solve? What product are we trying to create and why? How can we measure its success?
- Brainstorm for the chosen “approach”. Get together with the team and find a working solution in a few sessions. Do not overly delay with this phase. Start from the top level and gradually lower yourself down to the levels below.
- Draw your approach on the board. Gather the team together, and let one of you depict the approach to which the team is leaning. You should be able to clearly explain the architecture of your system / application using the whiteboard - starting from the very top level and, if necessary, going lower and lower. If you are having difficulty with any explanation or it is not clear enough for colleagues, then you need to better work out the details of your decision.
- Fix it in the form of simple documentation with simple diagrams based on what you previously explained with whiteboarding. Keep jargon to a minimum: you want even juniors to understand what is being said. Use a clear and easy language in your description . At Uber, we use an RFC-like document that is based on a simple template.
- Discuss tradeoffs and alternatives . Good software design and good architecture are the right compromises. In and of itself, none of the design choices is bad or good: it all depends on the context and goals. Is your architecture divided into different services? Mention why you decided not to make one big service, to which there could be other advantages like a simpler and faster deployment. Have you decided to expand the module or service with new functionality? Instead, weigh the possibility of developing a separate service or module, and what are the pros and cons of this approach.
- Distribute the design document within the team / organization and gather feedback. Previously, at Uber, we sent out all the design documents for the software to all our engineers - until the time when our staff grew to 2,000 people. Now that we have grown to such a scale, we are still trying to reach the largest possible audience - but at the same time we have begun to ensure that the level of information noise does not exceed the permissible limit. Encourage others to ask questions and suggest alternatives. Be pragmatic about time limits for discussing feedback and including it in the document where necessary. Specific wishes can be applied immediately and quickly, while detailed feedback is better to disassemble with a person in person on the spot.
Why is our approach different from what is usually mentioned in the literature on software architecture? In fact, our approach is not fundamentally different from what is described in the architecture manuals. Almost all of the manuals suggest starting with a business task, as well as describing solutions and trade-offs - we do this too. What we are not doing is not using more sophisticated tools that many architects and architecture books advocate for. We document design as simple as possible, using the simplest and most obvious tools, such as Google Docs or Office 365.
I believe that the main difference in our approach comes down to the engineering culture of different companies. A high degree of autonomy and a minimized hierarchy is a feature that is common among modern technology companies and startups; in the case of more traditional companies, this is far from always true. This is also the reason why in these places they often resort to “design based on common sense” instead of design based on a process with strict rules.
I know banks and car companies where developers are actively discouraged from making any architectural decisions without having to go up the chain and get approval from architects several levels higher, who oversee several teams. This makes the process slower, and with a large number of requests, architects become overloaded. Therefore, these architects create even more formal documents, in the hope that the system will become more understandable - using all the tools that the literature familiar to you describes. These documents are backed up by a top-down approach, since it acts in an intimidating way on engineers - after all, they are not architects, and should not doubt or dispute decisions, because they have already been documented using formal methods in which they are poorly versed. Therefore, they usually do not do so. Honestly, these companies do the same in order to make developers an interchangeable resource - because this allows them to transfer people from one project to another in a short time. It will probably not be a surprise for you that different tools are better suited for different environments.
Simple software design without jargon instead of architectural patterns
The goal of system design should be simplicity. The simpler the system, the easier it is to understand - and the easier it is to find problems in it and also to implement it. The clearer the language in which it is described, the more accessible its design. Avoid using jargon that every member of the team will not understand - even the most inexperienced of your colleagues should be able to understand what is at stake at the level of others.
A clean design is like clean code: it is easy to read and easy to understand. There are many great ways to write clean code. However, you will often hear that someone suggests starting with applying
“gang of four” design patterns to your code. Clean code begins with the principle of sole responsibility, clear names, and easy-to-understand conventions. These principles apply equally to pure architecture.
So what is the role of architectural patterns? I find them useful - just as useful as the code design patterns. They can give you ideas on how you can improve your code or architecture. Take code design patterns: I notice for myself when I notice a
singleton in the code - and I raise my eyebrows and dig deeper when I see a class that behaves like a
facade , but in fact it just makes calls. But the day has not yet come when I would have thought, "an
abstract factory is just asking for it here." In truth, it took me a long time to figure out what this pattern does before it dawned on me; understanding came as a result of hard work with
dependency injection , one of the few areas where this pattern
is widespread and extremely useful . I also admit that even though I spent a lot of time reading and understanding the “gang of four” design patterns, they had a much smaller impact on my professional development as a programmer than the feedback that I received from other developers regarding my code.
Likewise, knowing common architectural patterns is a good thing: it helps reduce the time spent in discussions with people who understand them in the same way that you do. But architectural patterns are not the goal, and they will not be a substitute for simpler system design options. When you design a system, you may suddenly realize that you accidentally applied some common pattern - and this is good, because later it will be easier for you to refer to the approach used. But you should never take one or more patterns and use them as a hammer, for which you will always see nails.
Architectural patterns came into being when engineers drew attention to the fact that in certain situations it makes sense to make similar decisions regarding design and its implementation. Over time, these decisions received names, were recorded and discussed by the general public. Architectural patterns are tools that appeared
after solutions were found, in the hope that this would help make life easier for others.
As an engineer, you should set as your goal an independent search for solutions to your problems and learn during this process - instead of taking a “hype” architectural pattern in the hope that this will solve your problem.
How to learn how to better design systems
People often ask me for advice: how to "pump" in the architecture and design of systems? Experienced engineers generally recommend reading about architectural patterns, as well as books on software architecture. I fully support the recommendation to read as much as possible - especially books, since they allow you to dive into the subject much deeper than short posts - however, I have a few more practical recommendations.
- Engage one of the team members and do whiteboarding together. Draw on the board what you are working on and explain the meaning of the image. Make sure your colleagues understand what is being said. And when you are finally understood, ask for feedback.
- Describe your design in a simple document and share it with your team by asking them for feedback. It does not matter how simple or complex the task you are working on - it can be either a small refactoring or a large-scale project - you should summarize it concisely. Do it in such a way that for you this document makes sense, and for the rest it is understandable; for inspiration - here's an example of how this is done in Uber . Share this document with your team in a format that allows for commenting - for example, using Google Docs, Office 365 or something similar. Ask others to supplement it with their thoughts and questions.
- Create two different designs and contrast them. When most of us design architecture, they usually go one way: the one that pops up in their head. However, architecture is not something black and white. Come up with a second architecture design option that might work in your situation too. Contrast both designs and explain why one is better than the other. Indicate that you have been considering the second option for some time as an alternative, and explain why you made a decision not in his favor.
- Decide on the compromises that you are ready to make - find out why you accept them and what you want to achieve with their help. Clearly establish the existing restrictions that you were forced to take into account.
- Conduct a review of other people's designs. And do it wisely. If your company has a culture of sharing designs that people share using whiteboarding, meetings or documents - get more from them. Usually during a review, people perceive someone else's code design as observers; instead, ask clarifying questions for parts that you don’t quite understand. Ask what alternatives they considered. Ask what compromises they made and what limitations they considered. Be the devil's advocate and suggest another, possibly simpler alternative — even if it is no better than their solution — and ask what they think about your proposal. Even though you haven’t thought out your design so well compared to the one who presents it, your discussion can still bring something new and help you better understand the task you are doing.
The best software design is simple and easy to understand. The next time you start a new project, instead of pondering the question "
How will I design this system, what patterns should I use in battle and with what formal methodology should I document it? ", Think - "
How can I come to the simplest possible design - one that will be easily understood by anyone? "
The best practices of software architecture, architectural patterns of enterprise applications, formal ways of describing systems are all tools that are useful to own, because one day they can be very useful to you. But
when designing systems, it’s worth starting with the simple and continuing to maintain maximum simplicity. Try to avoid the complexity that more complex architectures and formal tools inevitably bring to your systems.
This post caused a heated discussion among industry workers who shared their experiences and attitudes towards software architecture. You can review these discussions at Hacker News , Lobste.rs, and Reddit .
While most commentators agree with the main message of the article, others notice that in reality such an approach may be poorly applicable to the world of “bloody enterprise”, where architects “eat their bread” for good reason; they ask themselves what a system designed in such a way 20 years later will look like and recall the “theory of broken windows”, and also talk about the integration of 300 IT systems , which by definition cannot be simple - because each of them has a unique API, some systems works at Kobol and each of them serves from 5000 to 7000 operators.