Architectural Styles
This file explores different architectural styles and their applications in modern software development.
Monolithic Architecture​
Overview​
A monolithic architecture deploys all application components as a single deployable unit.
Characteristics​
- Single deployable artifact
- Shared database
- Centralized business logic
- Direct in-process communication
Example Structure​
monolithic-app/
├── src/
│ ├── controllers/
│ │ ├── UserController.java
│ │ ├── OrderController.java
│ │ └── ProductController.java
│ ├── services/
│ │ ├── UserService.java
│ │ ├── OrderService.java
│ │ └── ProductService.java
│ ├── repositories/
│ │ ├── UserRepository.java
│ │ ├── OrderRepository.java
│ │ └── ProductRepository.java
│ └── models/
│ ├── User.java
│ ├── Order.java
│ └── Product.java
├── database/
│ └── schema.sql
└── config/
└── application.properties
Advantages​
- Simple deployment: Single artifact to deploy
- Easy testing: Everything runs in one process
- Simple debugging: All code in one place
- No network latency: Direct method calls
- ACID transactions: Easy to maintain consistency
Disadvantages​
- Technology lock-in: Entire app uses same stack
- Scaling limitations: Must scale entire application
- Development bottlenecks: Large teams working on same codebase
- Fault tolerance: Single point of failure
- Deployment risk: Changes affect entire system
When to Use​
- Small to medium applications
- Simple business domains
- Small development teams
- Rapid prototyping
- Applications with strong consistency requirements
Microservices Architecture​
Overview​
Microservices architecture structures an application as a collection of loosely coupled, independently deployable services.
Characteristics​
- Service per business capability
- Decentralized governance
- Independent deployment
- Technology diversity
- Failure isolation
Example Structure​
microservices-ecommerce/
├── user-service/
│ ├── src/main/java/
│ ├── Dockerfile
│ └── k8s/
├── order-service/
│ ├── src/main/java/
│ ├── Dockerfile
│ └── k8s/
├── product-service/
│ ├── src/main/java/
│ ├── Dockerfile
│ └── k8s/
├── notification-service/
│ ├── src/main/java/
│ ├── Dockerfile
│ └── k8s/
├── api-gateway/
│ ├── src/main/java/
│ ├── Dockerfile
│ └── k8s/
└── docker-compose.yml
Service Communication Patterns​
Advantages​
- Technology diversity: Each service can use different technologies
- Independent scaling: Scale services independently
- Fault isolation: Failure in one service doesn't bring down others
- Team autonomy: Teams can work independently
- Faster deployment: Deploy services independently
Disadvantages​
- Distributed system complexity: Network calls, latency, failures
- Data consistency: Managing transactions across services
- Testing complexity: Integration testing is harder
- Operational overhead: More services to monitor and maintain
- Development overhead: Service discovery, configuration management
When to Use​
- Large, complex applications
- Multiple development teams
- Different scaling requirements per component
- Need for technology diversity
- Organizations with DevOps maturity
Service-Oriented Architecture (SOA)​
Overview​
SOA is an architectural pattern where application components provide services through well-defined interfaces.
Characteristics​
- Service contracts and interfaces
- Service registry and discovery
- Loose coupling
- Service composition
- Platform independence
SOA vs Microservices​
| Aspect | SOA | Microservices |
|---|---|---|
| Service Size | Larger services | Small, focused services |
| Communication | SOAP, ESB | HTTP/REST, messaging |
| Data Sharing | Shared databases | Database per service |
| Governance | Centralized | Decentralized |
Serverless Architecture​
Overview​
Serverless architecture runs applications in stateless compute containers managed by cloud providers.
Characteristics​
- No server management
- Event-driven execution
- Automatic scaling
- Pay-per-execution
- Stateless functions
Advantages​
- No infrastructure management: Focus on business logic
- Automatic scaling: Scales with demand
- Cost effective: Pay only for execution time
- High availability: Built-in fault tolerance
- Fast deployment: Quick to deploy functions
Disadvantages​
- Cold start latency: Initial execution delay
- Execution limits: Time and memory constraints
- Vendor lock-in: Platform-specific implementations
- Limited local testing: Hard to replicate cloud environment
- Debugging complexity: Distributed logging and monitoring
Event-Driven Architecture​
Overview​
Event-driven architecture uses events to trigger and communicate between decoupled services.
Components​
- Event Producers: Generate events
- Event Routers: Route events to consumers
- Event Consumers: Process events
- Event Store: Persist events
Implementation Patterns​
CQRS (Command Query Responsibility Segregation)​
// Command side
public interface Command {
}
public class CreateUserCommand implements Command {
private final String email;
private final String firstName;
private final String lastName;
// Constructor and getters
}
@Service
public class UserCommandHandler {
private final UserEventStore eventStore;
public void handle(CreateUserCommand command) {
String userId = UUID.randomUUID().toString();
UserCreatedEvent event = new UserCreatedEvent(
userId,
command.getEmail(),
command.getFirstName(),
command.getLastName()
);
eventStore.saveEvent(userId, event);
}
}
// Query side
@Entity
@Table(name = "user_view")
public class UserView {
@Id
private String id;
private String email;
private String fullName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Constructors, getters, setters
}
@Service
public class UserQueryHandler {
private final UserViewRepository repository;
@EventListener
public void on(UserCreatedEvent event) {
UserView view = new UserView();
view.setId(event.getUserId());
view.setEmail(event.getEmail());
view.setFullName(event.getFirstName() + " " + event.getLastName());
view.setCreatedAt(event.getOccurredOn());
view.setUpdatedAt(event.getOccurredOn());
repository.save(view);
}
@EventListener
public void on(UserEmailChangedEvent event) {
UserView view = repository.findById(event.getUserId())
.orElseThrow(() -> new IllegalStateException("User view not found"));
view.setEmail(event.getNewEmail());
view.setUpdatedAt(event.getOccurredOn());
repository.save(view);
}
public Optional<UserView> findById(String id) {
return repository.findById(id);
}
public List<UserView> findByEmail(String email) {
return repository.findByEmailContaining(email);
}
}
Layered Architecture​
Overview​
Layered architecture organizes code into horizontal layers with each layer only communicating with the layer directly below it.
Common Layers​
- Presentation Layer: UI, Controllers
- Business/Service Layer: Business logic
- Persistence Layer: Data access
- Database Layer: Data storage
Hexagonal Architecture (Ports and Adapters)​
Overview​
Hexagonal architecture isolates the core business logic from external concerns using ports and adapters.
Components​
- Domain: Core business logic
- Ports: Interfaces for communication
- Adapters: Implementations of ports
Choosing the Right Architecture​
Decision Matrix​
| Architecture | Team Size | Complexity | Scalability | Technology Diversity | Operational Complexity |
|---|---|---|---|---|---|
| Monolithic | Small | Low | Limited | Low | Low |
| Modular Monolith | Medium | Medium | Medium | Low | Low |
| Microservices | Large | High | High | High | High |
| Serverless | Variable | Medium | High | Medium | Medium |
Guidelines​
- Start Simple: Begin with monolithic architecture
- Evolve Gradually: Extract services when clear boundaries emerge
- Consider Team Structure: Conway's Law applies
- Evaluate Operational Capability: Can you manage distributed systems?
- Business Requirements: Does your domain benefit from service boundaries?
Each architectural style has its place. Choose based on your specific context, team capabilities, and business requirements rather than following trends.