#becauseItDepends Finding your inner Vulcan: unemotional decision making with pattern-style thinking
Teams often struggle with making architecture and design decisions. They often end up in endless discussions, which are often emotional and unproductive. The team might also make less than ideal decisions on the basis of personal preferences or fashion. Or worse, the team might be paralyzed by indecision and fail to make a decision at all.
This is very human.
Vulcans, on the other hand, are known for their logical and unemotional decision making. What’s more, according to ChatGPT:
They are also known for their ability to use patterns to make decisions.
Of course, there’s no evidence that Vulcans actually use patterns to make decisions, but it’s an excellent idea. In this article, I describe a pattern-based process for making design decisions more effectively. I also generalize the process to make it applicable to situations where there are no patterns.
Patterns help you make decisions
A pattern is reusable solution to a problem occurring in a context and its consequences It’s a relatively ancient idea. First described the 70s by the real world architect, Christopher Alexander. They were then popularized in the software community by the gang of four design patterns book in the mid 90s.
What makes patterns especially valuable is their structure. A pattern consists of the following elements:
- Context - the situation within which the pattern is applicable
- Problem - the problem that the pattern solves
- Forces - the issues or concerns that you must consider that can determine the suitability of a solution
- Solution - the solution that the pattern proposes
- Consequences - describes the consequences of applying the pattern
Patterns highlight consequences
A valuable part of pattern is its consequences. That’s because the pattern forces you to consider both the benefits and drawbacks of a solution. You cannot just focus on its benefits (if you like the solution) or its drawbacks (if you don’t like the solution).
A pattern is requires you to consider the pattern’s issues, which are the sub-problems that are created by applying this pattern. The issues highlight the fact that there are no silver bullets. They are also extra work that you must take on if you choose to apply the pattern. Fortunately, however, a pattern typically references successor patterns that solve those sub-problems, which simplifies the application of the pattern.
Finally, a pattern must also reference any alternative patterns, which are different ways of solving the same problem. A pattern cannot pretend that it’s the only solution to a problem. It must acknowledge that there are other solutions, which usually have different trade-offs.
Patterns sometimes form a pattern language
Sometimes the patterns that are related through the predecessor-successor relationship and the alternative relationship form a pattern language. A pattern language language is a collection of patterns that solve problems in a particular domain.
For example, nine years ago I created the Microservices pattern language with goal of helping architects use microservices more appropriately and effectively. There two top-level patterns are the Monolithic architecture and Microservice architecture patterns. They are alternative architectures for your application. All of the other patterns are direct or indirect successor patterns of the Microservice architecture pattern. They solve problems that you create for yourself by using microservices.
Let’s look at how you can use patterns (or pattern-style thinking) to make decisions.
Making decisions by applying pattern-style thinking
Here’s a seven step decision making process called #becauseItDepends that uses pattern-style thinking to solve a problem. It can be used whether or not you have patterns
- Identify the context
- Define the problem
- Define the criteria for assessing suitability of a solution
- Find the candidate patterns (or solutions) that solve the problem
- Evaluate the trade-offs of each candidate
- Apply the chosen pattern (or solution)
- Recursively solve the sub-problems
Let’s look at each of these steps.
Step 1. Identify the context
The first step is to understand your context. For example, what’s the nature of the application you are developing? Also, what are the past design decisions since they might constrain your options?
You are developing an enterprise application that consists of various microservices.
Step 2. Define the problem
The second step is to identify the problem that you are trying to solve. This is often obvious, but sometimes it’s not. For instance, you might be trying to solve a problem that’s a consequence of an earlier design decision. Sometimes, that’s the problem that needs to be solved differently.
How to design the
This query retrieves a consumer’s order history, which is scattered across multiple microservices:
Order Service- order details
Kitchen Service- status of each
Orderat the kitchen
Delivery Service- delivery information
Step 3. Define the criteria for assessing suitability of a solution
In this step, you need to define the criteria for evaluating and comparing solutions. What’s nice about a pattern is that it already describes the forces (a.k.a. issues) that you need to consider. You “simply” need to consider whether a given force is applicable to your context. And, if it is applicable, its relative importance.
If you are not using patterns, you need to devise your own. These are often various non-functional requirements (or -ilities), including maintainability, scalability, etc.
Step 4. Find the candidate patterns/solutions that solve the problem
The next step is to identify the patterns/solutions that solve the problem in your context.
Example candidate solutions:
The microservice architecture pattern languages contains two query patterns:
- API Composition - query the provider service and combine the results
- CQRS - define a ‘replica’ database, which implements the query and is kept up to date by subscribing to events published by the providers.
Step 5. Evaluate the trade-offs of each candidate
The fourth step is to evaluate the trade-offs of each pattern/solution. If you are using patterns then you need to assess and compare how each pattern’s consequences - benefits, drawbacks and issues. For example, a particular benefit or drawback of pattern may or may not be important in your context.
When designing the
findOrderHistory() query, the main forces that you need to consider are the dark energy and dark matter forces.
Here’s how the two patterns compare:
findOrderHistory() query, CQRS is a better choice.
API Composition would be too inefficient due to the numerous network roundtrips required to implemented filtering and sorting.
Step 6: Apply the chosen pattern
The fifth step is to apply the chosen pattern/solution. One challenge, however, is that there might not be a clear winner. You might need to investigate further by applying the patterns/solutions in a prototype.
Once you have made a decision you might want to document it with an Architecture Decision Record (ADR).
Order History Service, which implement the
findOrderHistory() query using the CQRS pattern.
Step 7: Recursively solve the sub-problems
The chosen pattern/solution has consequences. The consequences update the context. You also need to solve the pattern/solution’s sub-problems by recursively applying this decision making process.
In order to use the CQRS patterns, the provider services, which own the data returned by the query, must publish Domain Events when they update their data. This is the next problem that you must solve.
It’s important to follow a structured decision making process with objective criteria for evaluating the suitability of a solution. If you do so, you will be able to make better decisions. Your meetings will be more productive and fun. And, your application will live long and prosper!
Need help adopting microservices?
I provide consulting and training that can accelerate and derisk your adoption of microservices.