Welcome to another Sunday article where we dive into system design.
As I’ve already stated: we are going to learn the rules, follow the rules, and once you are an expert — bend the rules.
Let’s keep track of what we’ve learned so far, to keep things at our fingertips even if you haven’t read the previous article:
- A good system design speaks (how components interact)
- Avoid functional decomposition (what we were doing in universities)
- The client should not be the core business. Let the client be the client — not the system
- Reduce coupling between services as much as possible
- Decompose based on volatility
In this article, we will dive into volatility-based decomposition.
In the last blog, we compared volatility-based and functionally based decomposition of a mechanical car. Don’t worry if you missed that article — we’ll again compare a functionally decomposed trading system with a volatility-based trading software system. This same example is taken from the book The Righting Software by Juval Löwy. We’ll prove again why functional decomposition is bad and volatility-based decomposition is good. We will see that software engineering is art, not just tech — like many other fields.
Through these examples, I’m trying to convince my readers why the rules of The Method are fruitful. Once we’re done, we’ll start jumping into learning the exact steps of how to do this art. How to implement the rules in detail, with hands-on examples and real-world problems in upcoming blogs. So hold on.
A Functionally Decomposed Trading System Design
Functional decomposition is nothing but breaking a system into functional components. Each component is responsible for one function. For a stock trading application, this is the functional decomposition design — very obvious and what we’ve been taught in universities. Look at the components in the figure below carefully.
Flaws in this architecture:
- The client is responsible for implementing the logic. The client is no longer a client — it has become an abstraction of business logic. It’s a system. Frontend developers now don’t like their job anymore. Congratulations, you’ve made their job more complicated and senseless.
If a user wants to buy some stocks by selling others, they need to sell first and then buy. But during this process, prices might change — the sold stocks might drop in value or the ones to buy might get more expensive. This could mean the money from selling isn’t enough to buy the desired amount.
So, what should the system do? Options include:
- Buy fewer stocks than planned
- Sell more than originally intended
- Use extra money from the user’s cash account
- Cancel the entire transaction
- Ask the user what to do
The main point is that the system needs special business rules to decide — and right now, those rules are handled by the client.
Imagine migrating from a web application to a phone application. The whole business logic has to be rewritten. Frontend developers are now almost cursing the architect. They are maintaining versions and variations of clients.
The user receives an email for all the activities in selling stocks, buying stocks, and trade scheduling. Imagine adding SMS support as a new communication protocol — all the services (reminder: services here are not microservices, just components) need to be changed to adapt.
Imagine your application is now launching in another country. To support the currency and new laws of that country, you either rewrite all the services or carry around a bloated monolith full of complex, tangled logic.
Services like Analyzing and Reporting are tightly coupled with others. They contain the logic of services like Buying Stocks to observe user patterns. Reporting needs the business logic of other components to determine which scenarios can lead to what level of threat.
Imagine adding a new stock feed system. All services need to be updated with the new rules and data schema. A total disaster for developers — a bomb blast of changes.
Volatility-Based Decomposition of a Trading System
Before designing, it’s important to list the requirements at hand. The areas of volatility are decided by analyzing those requirements.
Let’s see how a good trading system would look if broken based on volatility. In the next article, we’ll discuss how exactly to think and break the system using volatility. Maybe I’ll add one or two rules to the list. For now, let’s look at what can be considered areas of volatility or change:
User volatility: The end user can check the current state of funds. They may alter, query, or report the state. It also includes the volatility of user types — e.g., admin or regular user.
Client application volatility: A simple user might be happy with a basic web page, while professionals may need dashboards, spreadsheet support, or even a mobile app. This is clearly an area of change.
Security volatility: How does a user authenticate? It could be simple ID/password, SSO, or passwordless login. Different OAuth clients may need to be integrated. Even authorization is volatile.
Notification volatility: Email? In-app notification? SMS? This is a volatile area. Even language support may be added later.
Storage volatility: Is the database cloud-based? Is the cache in-memory or server-based? Is it file storage or something else?
Connection and synchronization volatility: Why not keep connections asynchronous? The trader can send independent requests to the app and maximize profit. A messaging queue might be better than simple async calls. This is volatile.
Trade item volatility: Customers may want to trade stocks, commodities, bonds, currencies, or future contracts. The trade item itself is volatile.
Workflow volatility: If the trade item is volatile, so is the processing. The steps in trading stocks differ from those for bonds or currencies. So the trading workflow is volatile too — same for analysis workflows.
Market feed system volatility: Stock feeds can come from Bloomberg, Reuters, or internal simulations. The feed source is volatile.
Once you’ve identified the volatilities, you can decompose them into components. One such decomposition looks like this:
Note: Listing volatilities and creating components is rarely one-to-one. One component often encapsulates multiple volatilities. Transitioning from volatility areas to components involves some rules — and we’ll master them throughout this series. Some components may align with operational concepts like queuing or event publishing. A volatility can also be handled by a third-party service — e.g., OAuth for security, or an external service for notifications.
Let’s briefly discuss what volatility each component encapsulates:
Data Access Component: Encapsulates data storage volatility. It knows where the data is, what technology is used, and handles business logic for reading/writing. Other components don’t care about storage details — they just use the data access layer.
Storage: The physical or logical data store — database, file-based, cache, local or cloud.
Any change in storage will only affect the data access component, keeping other parts decoupled. This enables minimal impact when storage needs to change.
Notification Utility: Technology used for messaging — e.g., pub-sub, messaging queues, etc.
Trade Workflow Component: What is being traded (stocks, currencies)? What are the steps? What changes per country/currency? Even if trade items are fixed, workflows change — this component handles that. It may use a third-party tool to manage workflows, saving/loading workflow instances from workflow storage. It supports different workflows, devices, and durations (seconds to days), ensuring consistency.
Analysis Workflows: Follows a similar pattern as trading workflows. Encapsulates volatility in analysis logic and uses the same workflow storage.
Feed Access: Encapsulates volatility of market feed source and format.
Clients: Apps like Trader App A, Trader App B, and the Customer Portal — each handles how to best present data on their respective platforms.
Conclusion
Let’s add some new rules to our list. In the next Sunday article, we’ll dive deeper into the rules and observations for breaking systems into components after identifying volatility.
- A good system design speaks (how components interact)
- Avoid functional decomposition (what we were doing in universities)
- The client should not be the core business. Let the client be the client — not the system
- Reduce coupling between services as much as possible
- Decompose based on volatility — list the areas of volatility
- There is rarely a one-to-one mapping between a volatility area and a component
- Symmetry is a good sign of good design
Stay tuned!
Here are the links to previous articles in case you missed them: