5 Cloud Software Architecture Design Patterns Every Engineer Should Know
Make smarter technology investments with design patterns. Reduce costs, improve system reliability, and gain the agility to outpace your competition
Hello everyone, and welcome to another episode of Tech Setters where we discuss tech, technologies and computer science in general! Today, we're diving into a topic that can make a huge difference in your business's success – software design patterns. If you've ever felt frustrated by slow software development, systems that don't talk to each other well, or the cost of constantly fixing problems, this episode is for you.
It’s not a secret that nowadays the software is the heart and engine of many businesses. When that engine is poorly designed, everything slows down and costs go up. That's what it can be like to tackle complex software problems without the right tools. But what if there were proven ways to build software that's efficient, reliable, and easy to adapt as your business grows? That's where software design patterns come in.
I always like to describe design patterns as blueprints for solving common software challenges. They give us, developers (or the developers you work with) a toolkit of solutions, saving them from reinventing the wheel every time. This means projects get done faster, systems work more smoothly, and you avoid costly technical headaches.
Whether you're tech-savvy or not, understanding the basics helps you make smarter decisions about your technology investments. In this episode, we'll explore 5 essential patterns that can boost your bottom line. There are tons more out there, but today I'm sharing a few of my all-time favourites!
Asynchronous Request-Reply Pattern
The Idea: So the idea is quite simple – decouple processing for smoother, more efficient task handling. The Asynchronous Request-Reply pattern provides an elegant solution to a common problem in modern software development: slowdowns and bottlenecks caused by long-running processes.
The Principle: Separating the request and response stages in interactions for processes that do not require immediate responses enhances both the responsiveness and scalability of systems. By adopting an asynchronous method, it's possible to increase server-side concurrency and allocate tasks to be executed as resources become available.
How it Works:
The frontend initiates a task, receives an immediate acknowledgment, and continues with other operations.
Meanwhile, the backend diligently works on the request, releasing the reference pointing to a resourse that the client can poll to check for the result of the long running operation.
A status endpoint serves as a communication channel that enables the frontend or any other system to periodically check on the progress of a task until it receives a success or error result.
Benefits:
Enhanced Responsiveness: Users experience a snappier interface, as the frontend isn't held hostage by slow backend tasks.
Improved Scalability: Even under heavy load, the backend can gracefully process requests without impacting the user experience.
System Resiliency: Bottlenecks become less likely, leading to a more robust and reliable software architecture.
Technical Considerations: This pattern relies on well-structured HTTP calls, providing a clear path for engineers to implement in various systems.
Claim-Check pattern
The Idea: The Claim-Check pattern aims to address the challenges of handling large message payloads within messaging systems. Messaging systems are often optimised for frequent, small message exchanges. So what we can do is to store the bulky payload in an external location and sending only a reference (the claim-check) through the messaging system, this pattern significantly lightens the load, ensuring optimal system performance and flexibility. It's like dropping off your heavy luggage at a check-in counter and carrying a small claim ticket – you can move more freely until you need to retrieve your belongings.
The Principle: The core principle of the Claim-Check pattern is the separation of concerns between message transmission and payload storage/retrieval. This separation offers several key advantages:
Messaging systems are freed from the burden of storing and processing large payloads, enabling them to maintain high throughput and responsiveness.
The ability to handle large payloads is no longer constrained by the messaging system itself. The pattern enables the scaling of payload handling independently by selecting suitable external data stores that are best suited for this purpose.
The choice of the external data store can be tailored to the specific payload type, access patterns, and security requirements.
In scenarios where the payload contains sensitive information, storing it separately from the message (and the potentially less secure messaging system) adds a layer of protection.
How it Works:
The sender stores the large payload in a suitable external data store. A unique, secure token (claim-check) is generated, representing the stored payload's location, e.g., an AWS S3 bucket and key.
The sender transmits a small message containing the claim-check through the messaging system; this can be any form of communication from an HTTP endpoint to a Kafka event.
The receiver uses the claim-check to retrieve the payload directly from the external data store.
Benefits:
Handles large payloads that might exceed the capacity of messaging systems.
Reduces load on the messaging system, potentially improving message throughput and latency.
Sensitive data can be stored securely in the external store, with only the claim-check visible in the messaging system.
Opens an opportunity to significantly lower costs due to message sizes are greatly reduced.
Technical Considerations: It’s important to select a storage solution suited to the payload type and access policies. Ensure a secure, unique, and ideally human-unreadable token generation mechanism and implement lifecycle management (deletion, expiration) for stored payloads.
Valet Key pattern
The Idea: Enable clients to directly access specific resources in a data store for a limited time, maximising scalability and performance while maintaining a degree of security and control.
The Principle: Offloading data transfer operations from the application to the data store itself, using temporary, restricted-access tokens (valet keys) to manage client interactions. This reduces load on the application and can potentially improve costs associated with data movement.
How it Works:
A client application indicates the need to access a resource (read/write data) within the data store. In response the application generates a time-limited, resource-specific token (valet key), usually signed with a secret to prevent tampering.
The application securely delivers the valet key to the client (e.g., embedded in a URL). The client uses the valet key to directly interact with the data store, performing the necessary operations.
The application can optionally invalidate the key after the operation is complete, further limiting its usability.
A classic example is the direct upload of media files, such as images or videos, to cloud storage (eg.: AWS S3) without an application acting as a proxy layer in between.
Benefits:
The pattern removes the application as a performance bottleneck for data transfer, improving throughput and responsiveness. The benefit is most noticeable when client requests are frequent or large enough to significantly tax proxy resources.
Efficient use of data store resources and decreased network traffic can lead to potential cost savings.
Valet keys are particularly useful when the application has limited compute capacity for handling large data transfers.
While the application loses some direct control, access remains granular, temporary, and auditable.
Technical Considerations: Ensure robust mechanisms for access restrictions policies and key revocation / expiration. Log and monitor key-based operations for security analysis and potential billing purposes. Since the application is bypassed, implement thorough validation on any uploaded data to prevent malicious content. Also consider if the granularity of control provided by the data store's key/token mechanism aligns with your requirements (e.g., data size limits might be difficult to enforce).
Anti-corruption Layer pattern (ACL pattern)
The Idea: Introduce a layer to insulate application components or subsystems with mismatched models or technologies, preventing design compromises and allowing them to evolve independently. It's a strategy for managing technical debt, supporting incremental modernisation, and effectively force the adaptability in complex systems.
The Principle: Decoupling systems by creating a translation layer (the ACL) that mediates communication, safeguarding the integrity of the “modern“ system from the influence of legacy components or external systems. The core principle lies in establishing “translational boundaries“ that shield components of one part of the system from the idiosyncrasies of others.
How it Works:
Subsystem A (e.g., a modern application component) initiates a request using its own data models and conventions.
The Anti-Corruption Layer intercepts the request, transforming it into a format compatible with the target system's (Subsystem B) requirements and forwards the translated request to Subsystem B (e.g., legacy system).
Response handling comes with the same approach. The ACL receives the response from Subsystem B, again translating it into a representation that Subsystem A understands and finally delivers the translated response back to Subsystem A.
Benefits:
Prevents “corruption“ of new system design or components by outdated or poorly structured external dependencies, while still supporting this dependencies and all other existing legacy components.
Subsystems can evolve at their own pace without cascading changes, minimising technical debt.
Facilitates gradual migration of legacy systems without major upfront rewrites.
Isolates the complexity of integrating mismatched systems within the ACL, reducing complexity elsewhere.
Technical Considerations: It’s important to understand that ACL adds an extra step in communication, potentially impacting performance and itself becomes a component to manage, scale, and maintain. Moreover, if any distributed transactions are involved, they can be challenging to manage correctly across subsystem boundaries.
Backends for Frontends pattern (BFF pattern)
The Idea: Align dedicated backend services with specific frontend experiences to optimise performance, reduce development friction, and enhance overall system agility.
The Principle: Decoupling backend development from frontend development by recognising that clients (mobile apps, web browsers, external systems etc.) often have distinct needs in terms of data, API formats, and responsiveness. BFFs create focused interfaces tailored to each client type while maintaining the flexibility in team workloads and the integration of features.
How it Works:
Instead of a single, monolithic backend, multiple backends are created, each aligned with a particular frontend or client type. This decentralisation is a core principle, allowing teams to design services around specific business capabilities or frontend requirements.
Each BFF can be optimised in terms of data models, API endpoints, and technology choice (e.g., programming language) to suit its frontend's specific needs. For instance, a mobile frontend might require a different data format or smaller payload sizes compared to a desktop web frontend.
Development teams responsible for individual frontends gain more control over their backend dependencies. Changes can be made and deployed without the risk of breaking other client experiences.
Benefits:
BFFs fine-tune data payloads, reduce network chatter, and potentially utilise technologies that are best suited for the frontend's needs (e.g., lightweight protocols for mobile devices).
Teams work independently, with a clearer separation of concerns. This can improve productivity and release cadence.
Individual backends can evolve at their own pace. Experimentation with new technologies or backend designs is less risky. Potential failures or performance issues in one BFF are less likely to have a cascading impact across other client experiences.
Minimises request count and roundtrips, as it lets clients fetch data from several services (or microservices) in one go, cutting down on overhead and enhancing user experience.
Technical Considerations: BFFs should primarily focus on client-specific logic and transformations – core business logic is better handled elsewhere to maintain consistency. Managing multiple backends adds operational overhead compared to a single backend, but also allows engineering teams to align better in terms of ownership and communication. Carefully evaluate this tradeoff. And finally, while BFFs enable client-specific optimisations, broader unified optimisations that benefit all frontends might become more complex task.
TLDR;
If you're looking to improve the efficiency, scalability, and overall quality of your software systems, design patterns are a very powerful tool. These are proven solutions for common development problems.
The tricky part isn't always knowing the pattern, but rather accurately identifying the problem the pattern is meant to solve. That's why it's worth understanding the principles behind design patterns. And by doing so, you can:
Save time and money by avoiding reinventing the wheel and get projects done faster;
Prevent common pitfalls and build more reliable software that work smoothly and effectively;
Make your software flexible enough to adapt to changes and handle constantly changing business needs;
Design patterns give developers a common language, making it easier to collaborate effectively and work together as a one single unit.
The patterns I've introduced today are just the tip of the iceberg. There's a whole world of design patterns out there, ready to help you solve those tough software challenges. Whether you're a tech leader making investment decisions or a hands-on developer, the benefits of these are worth exploring.
Remember, investing in good software design is an investment in your future! Let me know if you'd like to learn more about specific patterns or how to apply them in your projects. Until next time, keep on coding, Tech Setters!
Explore more: