Software architecture defines the structure and behavior of systems, ensuring they meet requirements like scalability, maintainability, and performance. This guide covers 15 widely used patterns with explanations, trade-offs, and tools across Node.js/TypeScript, C#/.NET, Go, and Java.
1. Layered Architecture
Organizes a system into horizontal layers — presentation, business logic, data access — each depending only on the layer below. Promotes separation of concerns and makes layers independently testable.
Trade-offs: Inter-layer communication adds latency. Over-layering creates unnecessary complexity.
Tools: ASP.NET Core + EF Core + Blazor (C#/.NET) · Express.js + TypeORM + React (Node) · Spring Boot + JPA + Thymeleaf (Java) · Gin + GORM (Go)
2. Microservices Architecture
Divides a system into small, independently deployable services, each owning its data and business capability. Services communicate via APIs or messaging.
Trade-offs: Independent scaling and deployment, but significant distributed systems complexity — network latency, data consistency, service discovery, and DevOps overhead.
Tools: NestJS + MassTransit + Kafka (C#/.NET) · Spring Cloud + Hibernate + Kafka (Java) · Go Micro + gRPC + NATS (Go) · Docker + Kubernetes + Prometheus
3. Event-Driven Architecture (EDA)
Components communicate by producing and consuming events via a message broker. Producers and consumers are fully decoupled — each scales independently.
Trade-offs: High responsiveness and scalability, but eventual consistency and complex error handling (dead-letter queues, retries, schema evolution).
Tools: MassTransit + Azure Event Hubs (C#/.NET) · KafkaJS + EventStoreDB (Node) · Axon + Apache Kafka (Java) · Watermill + NATS (Go)
Example pattern — event sourcing:
Instead of updating a record directly, store events like OrderPlaced, ItemShipped. Replay events to reconstruct current state. Enables auditability and time-travel debugging.
4. Service-Oriented Architecture (SOA)
Organizes a system as a collection of independent, reusable services that communicate over a network via SOAP or REST. An ESB or API Gateway handles orchestration.
Trade-offs: Reusability and interoperability across heterogeneous systems. More complex governance than microservices — service contracts, versioning, security.
Tools: MuleSoft ESB · WSO2 · Kong API Gateway · Spring Boot + Eureka (Java) · ASP.NET Core + Steeltoe (C#/.NET)
5. Monolithic Architecture
All functionality in a single codebase, deployed as one unit. Simple to develop, test, and deploy. Suitable for small teams or early-stage products.
Trade-offs: Fast to start, but tight coupling makes scaling and independent deployment hard as the system grows. Consider a modular monolith — loosely coupled internal modules — as a middle ground before microservices.
Tools: ASP.NET Core + EF Core + Razor Pages (C#/.NET) · Express.js + Sequelize + Next.js (Node) · Spring Boot + JPA (Java) · Go standard library + GORM
6. Client-Server Architecture
Clients request services; servers provide them. The foundation of every networked application.
Key decisions:
- Stateless vs stateful — stateless servers scale horizontally (REST APIs); stateful servers maintain connections (WebSockets)
- Caching — CDNs cache static assets; Redis caches session data
- Load balancing — NGINX/AWS ELB distribute traffic across server instances
Tools: ASP.NET Core + SignalR (C#/.NET) · Express.js + Socket.IO (Node) · Spring Boot + WebSocket (Java) · Go net/http + Gorilla WebSocket
7. Model-View-Controller (MVC)
Separates an application into Model (data/logic), View (UI), and Controller (coordination). Promotes testability — each component can be tested in isolation.
Server-side MVC: ASP.NET Core MVC, Spring MVC, Rails — renders HTML on the server.
Client-side MVC: Angular, React — updates the DOM in the browser.
When it gets complex: Controllers become bloated in large apps. MVVM or Clean Architecture addresses this.
Tools: ASP.NET Core MVC + EF Core (C#/.NET) · Angular/React + Express (Node) · Spring MVC + JPA (Java) · Revel/Beego + GORM (Go)
8. Component-Based Architecture
Structures a system as reusable, self-contained UI components that interact through well-defined props and events. Central to React, Angular, Blazor, and Vue.
Trade-offs: Fast development, parallel team work, easy testing of isolated components. Shared state management adds complexity — Redux (React), NgRx (Angular), Fluxor (Blazor).
Tools: React + Redux + Storybook (Node) · Blazor + Fluxor (C#/.NET) · Vaadin/GWT (Java) · Templ (Go)
9. Domain-Driven Design (DDD)
Aligns software design with the business domain. Key building blocks:
| Concept | Description |
|---|---|
| Entity | Object with a unique identity (e.g., Customer) |
| Value Object | Describes an attribute, no identity (e.g., Address) |
| Aggregate | Cluster of entities with a root (e.g., Order + OrderItems) |
| Repository | Abstracts data access for aggregates |
| Bounded Context | Clear boundary for a domain model |
| Domain Event | Signals a meaningful state change (e.g., OrderPlaced) |
Trade-offs: Ideal for complex domains (finance, healthcare, logistics). Significant upfront investment in domain modeling. Overkill for simple CRUD apps.
Tools: MediatR + EF Core + Ardalis.Specification (C#/.NET) · NestJS + TypeORM (Node) · Axon Framework + JPA (Java) · Go structs + SQLx + Watermill
10. API Gateway Pattern
A single entry point for all client requests in a distributed system. Routes requests to services and handles cross-cutting concerns centrally.
Responsibilities: Authentication · Rate limiting · Request/response transformation · Response aggregation · Caching · Load balancing
Trade-offs: Simplifies client interactions but becomes a single point of failure — cluster it. Adds a network hop — minimize processing overhead.
Tools: Ocelot (C#/.NET) · Spring Cloud Gateway / Zuul (Java) · KrakenD / Traefik (Go) · Kong · Amazon API Gateway · NGINX · Envoy
11. Circuit Breaker Pattern
Prevents cascading failures by halting requests to a failing service and returning fallbacks until the service recovers.
Three states:
- Closed — normal operation, requests flow through
- Open — failure threshold exceeded, requests blocked, fallback returned
- Half-Open — timeout elapsed, one test request allowed; closes on success, reopens on failure
Example fallbacks: Return cached data · Redirect to alternative service · Return a degraded response
Tools: Polly (C#/.NET) · Resilience4j / Hystrix (Java) · Opossum (Node) · GoBreaker (Go) · Envoy (built-in)
12. Saga Pattern
Manages distributed transactions across microservices without two-phase commit. Breaks a transaction into local steps, each emitting an event to trigger the next. Failed steps trigger compensating transactions to undo previous work.
Two coordination styles:
- Choreography — services react to events, no central coordinator. Simple but hard to trace.
- Orchestration — a Saga Coordinator sends commands and tracks state. Easier to reason about, more coupling.
Example — order placement:
- Order Service: create order → emit
OrderCreated - Inventory Service: reserve items → emit
ItemsReserved - Payment Service: charge card → emit
PaymentProcessed - If payment fails → compensate: release items, cancel order
Tools: MassTransit + NServiceBus (C#/.NET) · Axon Framework (Java) · Eventuate Tram (Node) · Watermill (Go) · AWS Step Functions
13. CQRS (Command Query Responsibility Segregation)
Separates write operations (Commands) from read operations (Queries) into distinct models — each optimized for its workload.
Command side: POST /orders → validates → persists to write DB → emits event
Query side: GET /orders → reads from pre-built read model (Redis, MongoDB, Elasticsearch)
Trade-offs: Independent scaling of reads and writes. Paired with Event Sourcing, the read model rebuilds from events. Eventual consistency — read model may lag slightly behind write model. Significant complexity overhead — not worth it for simple CRUD.
Tools: MediatR + EventStoreDB + Marten (C#/.NET) · Axon + Elasticsearch (Java) · NestJS + Redis (Node) · Watermill + MongoDB (Go)
14. Publish-Subscribe Pattern
Publishers send messages to topics on a broker; subscribers consume from those topics. Producers and consumers have no direct knowledge of each other.
Ideal for: Real-time feeds · Stock price updates · IoT sensor data · Notification systems · Log aggregation
Trade-offs: Dynamic, decoupled scaling. Requires schema management (Confluent Schema Registry), dead-letter queues for failures, and monitoring for queue health.
Tools: MassTransit + Azure Service Bus (C#/.NET) · Spring Cloud Stream + Kafka (Java) · KafkaJS + Redis Pub/Sub (Node) · Watermill + NATS (Go)
15. API-First Approach
Design and publish the API specification before writing implementation code. The spec (OpenAPI/Swagger or GraphQL schema) becomes the contract between teams.
Benefits:
- Frontend and backend teams work in parallel against the same contract
- Mock servers simulate responses during frontend development
- Third-party integrations have clear, stable contracts
- Consistent error codes, schemas, and versioning from day one
Versioning strategy: Use URI versioning (/v1/payments, /v2/payments) to avoid breaking consumers.
Tools: Swashbuckle + HotChocolate (C#/.NET) · Swagger-jsdoc + Apollo Server (Node) · OpenAPI Generator + Spring GraphQL (Java) · oapi-codegen + gqlgen (Go) · Postman · SwaggerHub · Stoplight
Choosing the Right Pattern
| Need | Consider |
|---|---|
| Simple app, small team | Monolithic or Layered |
| Independent team scaling | Microservices |
| Real-time, async workflows | Event-Driven + Pub/Sub |
| Complex business domain | DDD |
| High read/write disparity | CQRS |
| Distributed transactions | Saga |
| Resilience in dependencies | Circuit Breaker |
| Multiple client types | API Gateway |
| Parallel frontend/backend | API-First |
No pattern is universally correct. The best architecture is the simplest one that satisfies your actual constraints.
Originally published on Medium.
Top comments (0)