Skip to main content

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​

AspectSOAMicroservices
Service SizeLarger servicesSmall, focused services
CommunicationSOAP, ESBHTTP/REST, messaging
Data SharingShared databasesDatabase per service
GovernanceCentralizedDecentralized

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​

  1. Presentation Layer: UI, Controllers
  2. Business/Service Layer: Business logic
  3. Persistence Layer: Data access
  4. 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​

ArchitectureTeam SizeComplexityScalabilityTechnology DiversityOperational Complexity
MonolithicSmallLowLimitedLowLow
Modular MonolithMediumMediumMediumLowLow
MicroservicesLargeHighHighHighHigh
ServerlessVariableMediumHighMediumMedium

Guidelines​

  1. Start Simple: Begin with monolithic architecture
  2. Evolve Gradually: Extract services when clear boundaries emerge
  3. Consider Team Structure: Conway's Law applies
  4. Evaluate Operational Capability: Can you manage distributed systems?
  5. 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.