Patterns in Context: A User Guide to Design Patterns

We use patterns every day. Patterns define how we get to work, make our food and do our jobs. In order to be more efficient and productive in the creation of software, we make use of architectural and design patterns.
Why patterns?
A pattern is a common response to a recurring problem that is an effective solution to that problem. Patterns are named to facilitate communication within the industry, so we’re all speaking the same language while we discuss solutions.
Remember that patterns are a solution template, not the solution itself. Think of it as a template to help jump start your solution. Patterns can be customized to work for your particular problem.
Anti-patterns are a common response to a problem that is neither an effective solution nor a productive one. Anti-patterns exist at all levels of a project or organization (design, coding, project management). They’re hard to avoid and easy to slip into by accident. One common example is over engineering (YAGNI – You ain’t gonna need it) – overthinking the problem such that you create an architecture that’s twice the size it needs to be with extraneous services, or trying to game out what the solution will look like 10 years from now instead of dealing with today’s needs and leaving it extensible.
Pattern Types
Patterns can be grouped into the following categories. The examples provided are a small sample of what is available.
Implementation Patterns – programming
- Structural examples: façade, composite, adaptor
- Behavioral examples: command, iterator, visitor
- Creational examples: Factory method, abstract factory, singleton
“Develop to an interface, not an implementation. Favor object composition over inheritance.” Design Patterns: Elements of Reusable Object-Oriented Software
Integration Patterns – system and component integration (how do I make my components talk to each other? What do I need to make this work?)
- Messaging/Event processing examples: pub-sub, point to point
- Direct Communication example: API
- File Transfer - example: SFTP
Architectural Styles and Patterns – enterprise systems, applications
- Structural examples: layered, pipes & filters, component-based styles
- Messaging examples: implicit invocation, asynchronous messaging, pub-sub
- Distributed examples: service-oriented, peer to peer, cloud computing
Non-functional Architectural Styles & Patterns:
- Availability examples: multi-region cloud or data center, multiple servers, stateless services
- Scalability examples: stateless services, messaging, load balancing
- Security examples: SSO delegator, zero trust, trust but verify
Using Patterns
Let’s look at an example. During discovery phase, we’re gathering requirements functional and non-functional and then examining any constraints. After going through the discovery phase, we have enough information to determine the styles & patterns that will be useful for the solution.
For this example, we’ll use a system with the following requirements and constraints:
- Real time events: Average: 1000 events/minute, Max 5000 events/minute (External System Source)
- Batch processing (external and internal feeds)
- UI
- Real-time views
- Data entry and validation
- System of Record API for internal systems
- External reporting via EDW
- Cloud based (AWS)
ADFS for authentication and authorization - Business day availability (M-F, 8-5)
- Batch must be completed by 6 am (other systems depend on the results)
- Batch can start no earlier than 4 am (other systems provide needed data)
- Average users: 200 unique. Max: 400 unique.
- Automated testing in pipelines
Knowing these requirements for the system, we approach this by first identifying the styles and patterns that best suit this system. For this example, we know that we’ll want to use the following:
- Distributed – break down the problem by business domain and function – segregation of responsibility into services
- Cloud
- Use managed services when possible
- If leveraging cloud-specific services, possibly wrap them in a Façade so we don’t tie the client to cloud implementation
- Container-first development
- Managed Kubernetes
- Automate everything
- Delegate SSO and let ADFS do the work of authenticating
- Event Processing
- In 2 years, 5 years, 10 years, how many users/events do you expect to have? Select a provider that will be able to grow with the system
Once we’ve identified these styles and patterns, we can spec out the system with the appropriate providers and tools. Part of this is considering what the client’s overall architecture is – and how the solution that we’re creating fits into it – as well as what their development and support resources are to make sure these are options they can maintain going forward.
- Integration with external feed
- SNS (Simple Notification Service)
- Distributed services
- Kubernetes helps us scale quickly and does things load balancing for us.
- React UI
- API for application use
- Sockets for real-time views
- Cloud based database
- RDS (PostgreSQL)
- Batch processing (AWS Batch)
- File based – S3/Lambda
- ETL process for EDW tables
- Cognito integration with ADFS
- Role-based access controls with UI and API calls.
With these elements in place, we can create an architectural diagram (below). This should cover, at a high level, the components and services of the system.
Domain Service
Next, we will spec out one domain service.
The service:
- Has one responsibility
- Handles a specific event type from an external system
- Translates messages
- Enriches messages
- Is stateless
- Can be a serverless component of the system (In our case, a lambda)
- Is part of an orchestration
- Passes handled event to the next service via message queue or direct API call
- Handles a specific event type from an external system
When we’re thinking about a system at the architectural level and looking at the services and how events move through the system, that’s orchestration. It’s patterns that form a workflow and control the flow of events.
Domain Service Decomposition
This domain service takes a specific event, translates it into the system domain, adds descriptive data points and passes it on to the next consumer. Below are the messaging patterns used. A concrete example would be a trade coming in from an external system. That trade is translated into the system’s view of a trade event: adding additional data, e.g. a processed timestamp or an internal id.
Messaging Patterns:
- Filter – Filters to only events we’re interested in, e.g. only trade events.
- Endpoint – Abstract destination: can be a message queue or another service’s API
- Translator – Data coming in from external system needs to be translated for our system
- Content Enricher – Add reference codes and content from our own system
- Event Driven Consumer – Only does something when an event arrives and immediately passes it off to the next step
- Canonical Data Model – Makes sure all services speak the same language.
Conclusion
This is an example of how we can take requirements and efficiently create a system that fulfills those requirements. The patterns used provide a starting point for implementation. In addition, architects and developers can quickly understand the design due to the common language of patterns.
References
- Patterns
- https://en.wikipedia.org/wiki/Software_design_pattern
- Gamma, Erich, Helm, Richard, Johnson, Ralph and Vlissides, John M.. Design Patterns: Elements of Reusable Object-Oriented Software. 1 : Addison-Wesley Professional, 1994.
- https://en.wikipedia.org/wiki/Enterprise_Integration_Patterns
- https://www.geeksforgeeks.org/difference-between-architectural-style-architectural-patterns-and-design-patterns/
- Anti-Patterns
- https://en.wikipedia.org/wiki/Anti-pattern
- https://www.bmc.com/blogs/anti-patterns-vs-patterns/
- Brown, William J.; Malveau, Raphael C.; McCormick, Hays W. "Skip"; Mowbray, Thomas J. (1998). Hudson, Theresa (ed.). AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis. John Wiley & Sons, ltd. ISBN 978-0-471-19713-3.