Are These Six Software Anti-patterns Killing Your Delivery?
Tips for better delivery cycles with better output and a happier team
Engineers understand that software needs constant care and feeding to avoid inefficient delivery practices. Stakeholders expect timely delivery of their new features and capabilities, and can get frustrated at long delivery cycles. As engineers we need to be careful not to become a feature factory, and be mindful that some software anti-patterns could be at play, killing efficient software delivery.
In this blog I share six common software anti-patterns that are easy to recognize, and some tips to avoid them. They are a combination of behavioral, organizational, and architectural anti-patterns. Ignoring these anti-patterns will result in longer delivery cycles, increased team coordination, overly complex architectures, frustrated engineers, stakeholders and customers.
Note: Throughout this blog, I use the word Domain in the Design Driven Domain (DDD) software context that describes boundaries and relationships between software models. Three example domains I use include pricing, payments, and properties. Martin Fowler describes DDD here as Bounded Contexts.
Anti-pattern 1 — The mixed domain
A mixed domain occurs when the lines between business function and capabilities start to blur. This in turn has an effect on the various software architectures and teams. An example could be pricing logic contains payment information and vice versa. Having clear boundaries, APIs and interfaces creates a level of loose coupling that is not only logical, but also enables faster feature delivery for all clients to consume.
Nobody creates a mixed domain intentionally, but I have seen this happen as result of these behaviors:
- delivering the fastest way due to deadlines
- delivering the easiest way, not necessarily the right way
- domains not having clear interface boundaries or owners
- following how things were done in the past
Why is this not ideal? Because now logic is spread across multiple code bases, teams, creating extra dependencies, coordination, competing priorities, complicating testing and operations. Ultimately over time, you’ll end up “if/else’ing” your way to a mixed domain architecture, eventually slowing down delivery.
Ask yourself and your teams these questions to help avoid this situation in the first place:
“Do you know where your domain starts and ends? If no, then have the conversation.”
“Are all the right teams involved? If not engage them.”
Anti-pattern 2 — Customer specific domains
You might ask why is a customer specific domain an anti-pattern? The answer is quite easy, its leads to code duplication. An example of this is when you have multiple customer segments (e.g. geography, customer segment, or device type) that all use a common domain (e.g. pricing). If not done right, customer specific domains may result in your architecture and teams following Conways’ law. Where the architecture mimics the organization and communication patterns of the teams building the software.
Soon enough you have multiple teams all building their own similar domain logic for their own specific customer segment. Business teams will pressure you to build for their particular customer segment first, and before you know it, you’ve built this anti-pattern into your organization, product and architecture. Next up your company wants to build a new common feature, and only now you realize the cost of coordination, alignment, fragmented architecture, complex testing, operations to deliver of your feature.
Get with your teams and leadership and work towards making domains that solve the business problems for all customer segments. This may result in some team reorganization and ownership conversations.
Anti-pattern 3 — Mixing user experience and domain logic
The mixing of user experiences and business domain logic is probably one of the most common patterns I’ve seen. And is especially prevalent when you have your software organization split into backend and front end teams.
Business specific logic that should be generic and centralized, ends up being served by a single experience. Resulting in customers getting a different experience depending upon the way they interact with your application, e.g. Mobile, Web, Voice or API. Have you noticed that sometimes a feature is only available on Web but not on Mobile? The reverse is also true where experience logic is placed in the domains, complicating the domain logic. Like the other anti-patterns, this causes duplication of logic, longer delivery times, operational overhead, complicated testing and an inconsistent customer experience.
My observations for why mixing domain and experience logic happens usually comes down to, “timing and resourcing”. When a business domain team becomes a bottle neck for delivery, and an experience team has a delivery timeframe to hit. The team with the additional resources and time constraint will build this domain logic in the experiences in an effort to hit their deadlines. Sounds like a perfect time to inner-source? However it’s often easier to work in a code base you are familiar with than someone else's code base.
Ask yourself and teams this questions to help avoid this anti-pattern:
“Can this business logic be reused to support multiple experiences or clients?” If so it might be residing in the experience by mistake.
Anti-pattern 4 — Avoid extra duplicative domains
The result of the mixing of user experience and domain logic, or lack of clear team ownership, can lead to the emergence of a new, extra, duplicative domain layer. This extra domain layer can confuse contextual boundaries between domain layers for calling clients. In fact entire teams can be built around these extra domains. They may start off as a BFF (Back-end For Front-end) to help the experiences teams consume domain capabilities. Or even as a failed migration that never completed resulting in an extra duplicative domain.
At first these can be convenient for a developers, but over time these extra domains become an additional place to house duplicate domain logic. Leading too many of the anti-patterns previously mentioned, such as logic duplication, unclear ownership, longer delivery times, increased team dependencies and alignment.
To avoid this anti-pattern, ask yourself and your teams this question:
“Do my clients need to access multiple places to get similar business capabilities?”
Anti-pattern 5 — Orphan domains
The orphan domain is a domain without a clear owner, with logic spread across different teams and architectures. This behavioral pattern can be disruptive, as it certainly leads to multiple teams for delivery, prioritization and alignment. This definitely has become more prevalent since the move to micro service architectures. Often resulting in closely related, partial domains solving similar problems and duplicating logic. When a domain is not clearly defined, and different parts of the domain are built and operated by various teams, confusion and frustration often follows.
As a company, you do not want the delivery of a feature to be delayed due to misalignment of multiple teams backlogs and priorities. So if you recognize a critical part of your software organization and architecture is without a clear owner, take the time to have the the conversations to address it.
“Can you define clear contextual boundaries and ownership between domains?”
Anti-pattern 6 — Non customer centric APIs
Domains contain business logic, rules, CRUD (Create, Read, Update, Delete) database operations and storage. Their goal is to support the business for all customers, brands and devices. But many domain teams can become shielded from direct customer use cases by product teams and the experience teams. Often resulting in non customer centric APIs anti-pattern.
When the domains APIs become customer centric, everyone benefits. It not only solves a customer need, but becomes simpler to use, maintain, operate and improves performance. The diagram below shows a use case where a partner would like to get all communication messages for a particular reservation. In this case the partner knows the reservation ID, but not the message IDs. So avoid having the clients do additional work to solve their actual need/problem.
Ask yourself: “What is it that the customer is trying to do?”
The below diagram show how getting the messages for a reservation can be done in a single call, vs having each experience orchestrating finding the messages for a particular reservation in multiple calls. A lot of complexity can be avoided in the domain logic when the customer centric use case are considered.
The six anti-patterns above have one thing in common: Not being aware of them is likely to slow down software delivery and efficiency for your team, organization and company, which in turn affects the company bottom line.
You could start by answering these questions with your team:
- What is our domain logic and what is our experience logic?
- Do we know our domain boundaries?
- Are multiple teams building similar logic?
- Are our domain APIs solving actual customer problems?
Identifying and addressing these anti-patterns early can help alleviate the pain and frustration for stakeholders involved in software delivery.
Thank you to Sarah Balman for creating the illustrations in this post.