Modular Architectures: 10 Principles for Building Evolutionary Systems

Waldemar NetoΒΉ, William CalderipeΒΉ

ΒΉTechLeads.club


Abstract

The software industry faces a fundamental granularity dilemma: traditional monoliths suffer from high local complexity and limited scalability, while microservices introduce premature operational complexity that often outweighs their benefits. Recent empirical evidence, including research conducted by Google teams (Ghemawat et al., 2023), demonstrates that microservices generate performance loss, reduced visibility, management difficulties, API freezing, and development slowdown. This work presents Modular Architectures as a formal framework positioned between these extremes, based on conscious separation between logical boundaries (modules) and physical boundaries (deployment). We propose 10 fundamental principles that form a cohesive framework for building systems that evolve sustainably: Well-Defined Boundaries, Composability, Independence, State Isolation, Explicit Communication, Replaceability, Deployment Independence, Individual Scale, Observability, and Fail Independence. Our main contribution is the formalization of these principles as an architectural model that enables gradual evolution of granularity, reducing cognitive load and maximizing delivery velocity without incurring premature complexity.

Keywords: software architecture, modularity, monorepos, microservices, Domain-Driven Design, software granularity


Table of Contents

  1. Introduction and Contextualization
  2. State of the Art and Limitations
  3. Modular Architectures - Formal Definition
  4. The 10 Fundamental Principles
  5. Implementation Patterns
  6. Organizational Governance
  7. Comparative Analysis and Trade-offs
  8. Related Work
  9. Conclusion
  10. References

1. Introduction and Contextualization

The evolution of software architectures in recent decades reflects profound changes in both technological infrastructure and organizational practices. From the traditional monoliths of the 1990s-2000s, through Service-Oriented Architecture (SOA) in the 2000s, to the microservices that dominated the 2010s, each paradigm brought promises of better organization, scalability, and maintainability.

1.1 The Granularity Problem in Software

Granularity defines the size and cohesion of units that compose a system β€” whether functions, classes, modules, or services. This is one of the most critical architectural decisions, as it directly impacts:

We can classify granularity into three levels:

1.2 Paradigm Shift: Cloud and Modern Tooling

The advent of cloud computing, containers, orchestration (Kubernetes), and serverless fundamentally changed architectural possibilities. For the first time, it became viable to:

Paradoxically, this flexibility created a new challenge: complexity migrated from code to distributed architecture. As observed by Khononov (2023) in β€œBalancing Coupling in Software Design,” complexity in modern systems emerges primarily from communication between components, not from the internal code of each one.

1.3 Local vs Global Complexity

In traditional monolithic systems, complexity was local: infrastructure changes (database, frameworks) impacted the entire system. The response was rigid abstraction layers and patterns like Hexagonal Architecture and Clean Architecture.

In modern distributed systems, complexity is global: the challenge is not protecting the domain from the ORM, but ensuring clear contracts between services, managing eventual consistency, observability, and deployment orchestration.

1.4 Objective of This Work

This paper proposes Modular Architectures as an intermediate framework that:

  1. Maintains the visibility and collaboration of monoliths
  2. Offers the autonomy and scalability of microservices
  3. Avoids premature operational complexity
  4. Enables gradual evolution according to organizational maturity

We formalize this concept through 10 fundamental principles empirically validated in enterprise projects at companies like Atlassian, BuildOps, and various high-growth startups.


2. State of the Art and Limitations

2.1 Traditional Monoliths

Characteristics:

Benefits:

Limitations:

2.2 Microservices: Promises vs Reality

Microservices architecture was widely adopted with promises of:

2.3 Empirical Evidence of Problems

The article β€œTowards Modern Development of Cloud Applications” (Ghemawat et al., 2023), published at HotOS and developed by Google researchers, including Sanjay Ghemawat (co-creator of MapReduce and Google File System), identifies five systemic problems in microservices:

2.3.1 Performance Loss

Data serialization and network calls add significant overhead compared to local calls. The more fragmented the system, the higher the communication cost.

2.3.2 Loss of Visibility

With multiple versions of multiple services in production simultaneously, it becomes extremely difficult to predict emergent behaviors. Most critical failures occur in interactions between different versions of services.

2.3.3 Management Difficulty

Each service becomes an independent binary with its own build, test, and deployment process. The effort to keep everything working together grows exponentially.

2.3.4 API Freezing

When publishing an API between services, you freeze its evolution. Old APIs persist indefinitely because there’s no visibility of who consumes them, creating cumulative technical debt.

2.3.5 Development Slowdown

Changes affecting multiple services cannot be made atomically. Developers need to orchestrate changes across N services with misaligned versions, drastically reducing delivery velocity.

2.4 The Fundamental Error

According to Ghemawat et al. (2023), the error of microservices is in conflating logical boundaries (code) with physical boundaries (deployment):

2.5 Comparative Table: Granularity

Criteria Coarse (Monolith) Medium (Modular) Fine (Microservices)
Operational Complexity Low Medium High
Initial Velocity High High Low
Scalability Limited Flexible Maximum
System Visibility Total High Fragmented
Deployment Single Per app Per service
Coupling High Controlled Distributed
Required Governance Low Medium High
graph LR
    A[Monolith<br/>Complexity<br/>Medium] --> B[Modular<br/>Complexity<br/>Minimal]
    B --> C[Microservices<br/>Complexity<br/>High]

Figure 1: Operational Complexity Sweet Spot
Modular Architectures occupy the point of minimal operational complexity, balancing the benefits of monoliths (simplicity) with those of microservices (autonomy), avoiding the problems of both extremes.

2.6 Identified Gap

There is no formal framework that allows:

Modular Architectures fill this gap.


3. Modular Architectures - Formal Definition

3.1 Definition

Modular Architecture is a software system that establishes explicit separation between logical boundaries (modules) and physical boundaries (processes/deployments), allowing granularity to evolve incrementally and reversibly according to business needs and organizational maturity.

3.2 Fundamental Characteristics

3.2.1 Modularization in Monorepo

All code lives in a single versioned repository, organized into:

3.2.2 Independent Deployment as an Option

Modules can be deployed together (modular monolith) or separately (dedicated apps), without requiring code refactoring. The deployment decision is operational, not structural.

3.2.3 Gradual Evolution

The architecture supports natural transitions:

Modular Monolith β†’ Modules with Clear Boundaries β†’ 
Dedicated Apps β†’ Microservices (when justified)

3.3 Architectural Components

3.3.1 Apps (Bootstrap Points)

Apps are executables that import and initialize modules. They contain no business logic.

apps/
  monolith/              # Imports all modules
    main.ts              # Bootstrap
    monolith.module.ts   # Orchestration
  billing-api/           # Imports only Billing
    main.ts
    billing-api.module.ts

3.3.2 Packages (Logical Units)

Domain Modules: encapsulate business rules of a Bounded Context (DDD)

packages/
  billing/
    core/               # Entities, use cases
    persistence/        # Repositories, migrations
    http/              # Controllers, DTOs
    __test__/          # Isolated tests

Infrastructure Modules: provide reusable technical capabilities

packages/
  shared/
    logger/
    config/
    database/

3.3.3 Explicit Dependency Graph

Tools like Nx automatically build a graph showing how modules relate, enabling:

monorepo-root/
β”‚
β”œβ”€β”€ apps/                          [Apps = Bootstrap]
β”‚   β”œβ”€β”€ monolith/                     ← Imports all modules
β”‚   β”‚   β”œβ”€β”€ main.ts
β”‚   β”‚   └── monolith.module.ts
β”‚   β”‚
β”‚   β”œβ”€β”€ billing-api/                  ← Only Billing
β”‚   β”‚   β”œβ”€β”€ main.ts
β”‚   β”‚   └── billing-api.module.ts
β”‚   β”‚
β”‚   └── content-worker/               ← Only Content/VideoProcessor
β”‚       β”œβ”€β”€ main.ts
β”‚       └── content-worker.module.ts
β”‚
β”œβ”€β”€ packages/                      [Packages = Logic]
β”‚   β”‚
β”‚   β”œβ”€β”€ DOMAINS
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ billing/                  ← Bounded Context
β”‚   β”‚   β”‚   β”œβ”€β”€ core/                 (entities, use cases)
β”‚   β”‚   β”‚   β”œβ”€β”€ persistence/          (repositories, migrations)
β”‚   β”‚   β”‚   β”œβ”€β”€ http/                 (controllers, DTOs)
β”‚   β”‚   β”‚   └── __test__/
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ content/
β”‚   β”‚   β”‚   β”œβ”€β”€ catalog/              ← Submodule
β”‚   β”‚   β”‚   β”œβ”€β”€ video-processor/      ← Submodule
β”‚   β”‚   β”‚   └── admin/                ← Submodule
β”‚   β”‚   β”‚
β”‚   β”‚   └── identity/
β”‚   β”‚       β”œβ”€β”€ core/
β”‚   β”‚       β”œβ”€β”€ persistence/
β”‚   β”‚       └── http/
β”‚   β”‚
β”‚   └── INFRASTRUCTURE
β”‚       └── shared/
β”‚           β”œβ”€β”€ logger/               (no business logic)
β”‚           β”œβ”€β”€ config/
β”‚           └── database/
β”‚
β”œβ”€β”€ tools/
β”‚   └── scripts/
β”‚
β”œβ”€β”€ nx.json                           [Nx Configuration]
β”œβ”€β”€ package.json
└── tsconfig.base.json

3.4 Enabling Tooling

Modular Architectures are enabled by modern tools:

Technology Capability Examples
Monorepo Tools Dependency management, incremental builds Nx, Bazel, Turborepo
Modular Frameworks Composition via modules, dependency injection NestJS, Spring Boot, .NET
Native Modularization Module systems in languages Java Modules, Go packages
Integrated Solutions Flexible deployment maintaining modularity Ktor, Maven Packages, Java Modulith

3.5 Fundamental Differentiation

Aspect Microservices Modular Architectures
Deployment Decision Defined in architecture Separated from architecture
Granularity Fixed from the start Evolves gradually
Reversibility Difficult Natural
Initial Complexity High Low
Visibility Fragmented Total (monorepo)
graph TB
    subgraph apps [Apps - Bootstrap]
        monolith[monolith/]
        billing_api[billing-api/]
        content_worker[content-worker/]
    end
    
    subgraph packages [Packages - Logic]
        subgraph domain [Domains]
            billing[billing/]
            content[content/]
            identity[identity/]
        end
        
        subgraph infra [Infrastructure]
            shared[shared/]
        end
    end
    
    monolith --> billing
    monolith --> content
    monolith --> identity
    monolith --> shared
    
    billing_api --> billing
    billing_api --> shared
    
    content_worker --> content
    content_worker --> shared
    
    style monolith fill:#4A90E2
    style billing_api fill:#4A90E2
    style content_worker fill:#4A90E2
    style billing fill:#50C878
    style content fill:#50C878
    style identity fill:#50C878
    style shared fill:#9CA3AF

4. The 10 Fundamental Principles

This framework is structured into four categories of principles, each addressing a fundamental aspect of modularity.

4.1 Structural Principles

Define the foundation of modular organization.

4.1.1 Well-Defined Boundaries

Definition: Each module has clear responsibilities aligned to a Bounded Context (DDD), without exposing internal details to other modules.

Rationale: Clear boundaries reduce accidental coupling, facilitate maintenance, and enable independent evolution. They follow the encapsulation principle extended to the architectural level.

Implementation:

The following examples show module setup in different languages. The concept applies to any technology - modules can be implemented with or without frameworks, using native language organization and encapsulation conventions.

NestJS (TypeScript) - with framework:

// packages/billing/billing.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([SubscriptionEntity])],
  providers: [BillingService],
  controllers: [BillingController],
  exports: []  // Nothing exported - communication via HTTP/events
})
export class BillingModule {}

Go (without framework) - conventions only:

// packages/billing/module.go
package billing

// Module setup - public initialization function
func Setup(router *mux.Router, db *sql.DB) {
    // Internal components (not exported)
    repo := newRepository(db)
    service := newService(repo)
    
    // Only HTTP routes are exposed
    setupRoutes(router, service)
}

Ktor (Kotlin) - conventions + extension functions:

// packages/billing/BillingModule.kt
package com.example.billing

fun Application.billingModule() {
    // Internal components
    val repository = BillingRepository(database)
    val service = BillingService(repository)
    
    // Only routes are exposed
    routing {
        billingRoutes(service)
    }
}

Universal principle: The module is an organizational unit that:

Common violation: Modules that export repositories or internal entities allow others to depend on implementation details.

4.1.2 Composability

Definition: Modules are designed as reusable blocks that can be combined to form different applications or functionalities.

Rationale: Maximizes reuse, reduces duplication, and enables incremental evolution without rewriting code.

Implementation:

// monolith.module.ts (imports everything)
@Module({
  imports: [BillingModule, ContentModule, IdentityModule, LoggerModule]
})
export class MonolithModule {}

// billing-api.module.ts (imports only billing)
@Module({
  imports: [BillingModule, LoggerModule]
})
export class BillingApiModule {}

Benefit: Same modules can be orchestrated in different ways without duplicating code.

4.1.3 Independence

Definition: Modules are autonomous in code and infrastructure, communicating only via explicit APIs, events, or asynchronous messages.

Rationale: Prevents accidental coupling, enables independent technological evolution, and facilitates isolated testing.

Implementation:

Rule: If module A needs data from module B, use API or event β€” never import classes from B directly.

4.2 Operational Principles

Define how modules interact.

4.2.1 State Isolation

Definition: Each module manages its own state (database, cache, queues) without direct sharing with other modules.

Rationale: Prevents cascading failures, enables independent scalability, and facilitates schema evolution.

Implementation:

graph TB
    subgraph "Billing Module"
        BillingService[Billing Service]
        BillingRepo[Billing Repository]
    end
    
    subgraph "Identity Module"
        IdentityService[Identity Service]
        IdentityRepo[Identity Repository]
    end
    
    subgraph "Database"
        subgraph "Schema/DB Billing"
            BillingTables["Tables:<br/>β€’ subscriptions<br/>β€’ payments<br/>β€’ invoices"]
        end
        
        subgraph "Schema/DB Identity"
            IdentityTables["Tables:<br/>β€’ users<br/>β€’ sessions<br/>β€’ roles"]
        end
    end
    
    BillingService --> BillingRepo
    BillingRepo --> BillingTables
    
    IdentityService --> IdentityRepo
    IdentityRepo --> IdentityTables
    
    BillingRepo -.->|❌ Never accesses| IdentityTables
    IdentityRepo -.->|❌ Never accesses| BillingTables
    
    style BillingService fill:#4A90E2
    style IdentityService fill:#50C878
    style BillingTables fill:#FFE5B4
    style IdentityTables fill:#E0FFE0
    style BillingRepo fill:#4A90E2
    style IdentityRepo fill:#50C878

Figure: State Isolation between Modules
Each module accesses exclusively its own tables, without direct state sharing. Communication between modules occurs only via APIs or events.

Database strategies:

4.2.2 Explicit Communication

Definition: All communication between modules occurs via well-defined contracts (interfaces, DTOs, event schemas).

Rationale: Avoids implicit coupling, enables contract versioning, and facilitates testing with mocks.

Implementation:

Local (same process):

// Public interface
export interface BillingAPI {
  createSubscription(userId: string, planId: string): Promise<Subscription>;
}

// Implementation
@Injectable()
export class BillingService implements BillingAPI {
  async createSubscription(...) { /* logic */ }
}

Remote (separate processes):

@Controller('billing')
export class BillingController {
  @Post('subscribe')
  async createSubscription(@Body() dto: CreateSubscriptionDto) { /* ... */ }
}

Asynchronous (events):

@OnEvent('billing.subscription.created')
handleSubscriptionCreated(payload: SubscriptionCreatedEvent) { /* ... */ }

4.2.3 Replaceability

Definition: Modules can be removed, changed, or replaced without affecting the rest of the architecture.

Rationale: Enables technological experimentation, reduces vendor lock-in, and facilitates refactoring.

Implementation:

Example: Switch ORM (TypeORM β†’ Prisma) within a module without impacting consumers.

4.3 Deployment Principles

Define deployment flexibility.

4.3.1 Deployment Independence

Definition: Each app can be versioned and deployed in isolation, without forcing deployments of other apps.

Rationale: Reduces change risk, enables differentiated release cycles, and facilitates rollbacks.

Implementation (Example):

# CI/CD Pipeline (GitHub Actions example)
name: Deploy Billing API
on:
  push:
    branches: [main]
jobs:
  check-affected:
    runs-on: ubuntu-latest
    steps:
      - run: npx nx affected:apps --base=origin/main
      - if: contains(steps.affected.outputs.apps, 'billing-api')
        run: npx nx build billing-api && npm run deploy:billing-api

Strategy: Use nx affected to detect changes and deploy only impacted apps.

4.3.2 Individual Scale

Definition: Each app can scale horizontally according to its specific demand, without affecting others.

Rationale: Optimizes resource usage and prevents local bottlenecks from impacting the entire system.

Strategy: Metrics per app determine independent scaling.

4.4 Resilience Principles

Define reliable production operation.

4.4.1 Observability

Definition: Each module has its own logs, metrics, and tracing, enabling isolated diagnosis.

Rationale: Identifies problems quickly without searching the entire system.

Implementation:

// Logs with module context
logger.log('Subscription created', { 
  module: 'billing',
  userId, 
  planId 
});

// Metrics per module
@Metrics({ module: 'billing' })
export class BillingService { /* ... */ }

Tooling: Prometheus (metrics), Grafana (dashboards), Jaeger (distributed tracing).

4.4.2 Fail Independence

Definition: Failures in one module don’t propagate, ensuring controlled system degradation.

Rationale: Maximizes availability and facilitates recovery.

Implementation:

// Circuit Breaker
import CircuitBreaker from 'opossum';

@Injectable()
export class BillingClient {
  private breaker = new CircuitBreaker(
    () => this.callBillingAPI(),
    { timeout: 5000, errorThresholdPercentage: 50 }
  );
  
  async getBillingData() {
    try {
      return await this.breaker.fire();
    } catch (err) {
      // Fallback
      return { status: 'degraded', data: null };
    }
  }
}

Strategies: Circuit breakers, timeouts, retries with exponential backoff, fallbacks.

The 10 Principles Organized

graph TB
    subgraph resilience [Resilience]
        P9[9. Observability]
        P10[10. Fail Independence]
    end
    
    subgraph deploy [Deployment]
        P7[7. Deployment Independence]
        P8[8. Individual Scale]
    end
    
    subgraph operational [Operational]
        P4[4. State Isolation]
        P5[5. Explicit Communication]
        P6[6. Replaceability]
    end
    
    subgraph structural [Structural]
        P1[1. Well-Defined Boundaries]
        P2[2. Composability]
        P3[3. Independence]
    end
    
    P1 --> P4
    P2 --> P5
    P3 --> P6
    
    P4 --> P7
    P5 --> P8
    P6 --> P7
    
    P7 --> P9
    P8 --> P10
    
    style resilience fill:#EF4444,color:#fff
    style deploy fill:#F97316,color:#fff
    style operational fill:#3B82F6,color:#fff
    style structural fill:#10B981,color:#fff

5. Implementation Patterns

5.1 Types of Modules

5.1.1 Domain Modules

Represent Bounded Contexts (DDD) and encapsulate specific business rules.

Examples: billing/, content/, identity/

Typical structure:

billing/
β”œβ”€β”€ core/              # Entities, services, use cases
β”œβ”€β”€ persistence/       # Repositories, migrations
β”œβ”€β”€ http/              # Controllers, DTOs
└── __test__/          # Unit and E2E tests

5.1.2 Infrastructure Modules (Shared)

Provide reusable technical capabilities, without business logic.

Examples: shared/logger/, shared/config/, shared/database/

Rule: Never include business rules in shared. If two domains share logic, it indicates a poorly defined boundary.

5.2 Submodules

When domains become too large, divide them into submodules while maintaining Bounded Context cohesion.

Example:

content/
β”œβ”€β”€ catalog/           # Submodule: catalog management
β”œβ”€β”€ video-processor/   # Submodule: video processing
└── admin/             # Submodule: content administration

Benefits:

Rule: Submodules can share entities and repositories, but never services or application logic.

5.3 App Design Patterns

5.3.1 Pattern 1: Shared App + Domain Apps

Structure:

apps/
  monolith/            # Shared app (all modules)
  billing-api/         # Dedicated app (only Billing)
  content-worker/      # Dedicated app (only Content/VideoProcessor)

When to use:

Advantages:

Challenges:

5.3.2 Pattern 2: Apps per Domain

Structure:

apps/
  billing-app/         # Only Billing modules
  content-app/         # Only Content modules
  identity-app/        # Only Identity modules

When to use:

Advantages:

Challenges:

5.4 Evolution Strategy

Phase 1: Modular Monolith

Phase 2: Modules with Clear Boundaries

Phase 3: Dedicated Apps

Phase 4: Extraction to Microservices

timeline
    title Evolution of Modular Architectures
    
    section Phase 1
        Modular Monolith : 1 app
                          : All modules
                          : Maximum velocity
    
    section Phase 2
        Clear Boundaries : Enforce boundaries
                          : Observability per module
                          : Feature flags
    
    section Phase 3
        Dedicated Apps : 2+ apps
                       : Independent deployment
                       : Selective scaling
    
    section Phase 4
        Microservices : Many teams
                       : Network mandatory
                       : Maximum autonomy

6. Organizational Governance

6.1 Inverse Conway Architecture

Conway’s Law establishes that systems reflect organizational structures. In Modular Architectures, we invert this: code structure influences organization.

Well-defined modules naturally create:

6.2 Tech Lead Responsibilities

Technical leaders in modular architectures must:

  1. Establish Separation Guidelines
    • When to create a new module
    • When to promote a module to a dedicated app
    • Cohesion and coupling criteria
  2. Ensure Observability per Module
    • Dedicated dashboards
    • Alerts directed to responsible teams
    • Incident playbooks
  3. Balance Autonomy and Coherence
    • Autonomy: teams choose implementations
    • Coherence: contracts and patterns are consistent
  4. Manage Architectural Evolution
    • Review dependency graph periodically
    • Identify accidental coupling
    • Facilitate boundary refactorings

6.3 Versioning: Fast Forward vs Time Machine

6.3.1 Fast Forward (Monorepo)

6.3.2 Time Machine (Polyrepo)

Recommendation: Fast Forward for evolutionary architectures.

6.4 Operational Challenges

6.4.1 Shared On-Call

Problem: Who handles alerts when multiple teams share an app?

Solution:

6.4.2 Dependency Management

Problem: Modules can create circular or unwanted dependencies.

Solution:

// nx.json - enforce-module-boundaries
{
  "tags": {
    "domain:billing": ["domain:billing", "shared"],
    "domain:content": ["domain:content", "shared"],
    "shared": []
  }
}

Rule: Domains only depend on shared, never on each other.

6.4.3 Feature Flags for Joint Deployments

Problem: Shared app deployments affect multiple teams.

Solution:

if (featureFlags.isEnabled('billing-new-checkout', userId)) {
  return newCheckoutFlow();
}
return legacyCheckoutFlow();

7. Comparative Analysis and Trade-offs

7.1 Detailed Comparative Table

Criteria Monolith Modular Microservices
Operational Complexity Low Medium High
Initial Velocity High High Low
Scalability Vertical Selective horizontal Total horizontal
Visibility Total Total (monorepo) Fragmented
Deployment Single Per app Per service
Coupling High Controlled Distributed
Cognitive Load High (local) Medium High (global)
Operational Cost Low Medium High
Team Autonomy Low Medium-High High
Reversibility Difficult Easy Very difficult

7.2 When to Use Modular Architectures

Yes, when:

7.3 When NOT to Use

Consider alternatives when:

Prefer Simple Monolith:

Prefer Microservices:

7.4 Identified Limitations

  1. Requires Governance Discipline
    • Without enforcement, boundaries degrade
    • Requires active technical leaders
  2. Specific Tooling
    • Nx, Bazel, or equivalent are necessary
    • Initial learning curve
  3. Shared Database
    • If using single database, depends on discipline for isolation
    • No physical guarantees of separation
  4. Monorepo Culture
    • Teams used to polyrepo may resist
    • Requires cultural change

8.1 Domain-Driven Design (Evans, 2003)

Contribution: Concept of Bounded Contexts as modeling units.

Relation: Modular Architectures implement Bounded Contexts as domain modules, with technical boundaries beyond conceptual ones.

Reference: Evans, E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley.

8.2 Towards Modern Development of Cloud Applications (Ghemawat et al., 2023)

Contribution: Empirical evidence of systemic problems in microservices.

Relation: Validates the need to separate logical from physical boundaries, conceptual basis of this work.

Reference: Ghemawat, S., et al. (2023). Towards Modern Development of Cloud Applications. HotOS β€˜23.

8.3 Java Modulith (Spring Ecosystem)

Contribution: Modularity in the Java Spring Boot ecosystem.

Relation: Implements similar principles of Bounded Contexts in Java monorepos.

Difference: Specific to Java; this work generalizes to multiple languages.

8.4 Balancing Coupling in Software Design (Khononov, 2023)

Contribution: Analysis of global vs local complexity in distributed systems.

Relation: Substantiates the argument that modern complexity is in communication, not internal code.

Reference: Khononov, V. (2023). Balancing Coupling in Software Design. Addison-Wesley.

8.5 Nx Documentation (Nrwl)

Contribution: Monorepo practices and patterns focused on modularity.

Relation: Tooling that enables practical implementation of principles.

Reference: Nrwl. (2024). Nx Documentation. https://nx.dev

8.6 NestJS (Kamil Mysliwiec)

Contribution: Node.js framework with modular architecture inspired by Angular, promoting clear separation of responsibilities via modules, dependency injection, and decorators.

Relation: Practical example of framework that facilitates implementation of modular architectures, demonstrating how modular structures can be implemented natively in modern ecosystems.

Reference: Mysliwiec, K. et al. (2024). NestJS - A progressive Node.js framework. https://nestjs.com

8.7 Strategic Monoliths and Microservices (Vernon, 2021)

Contribution: Pragmatic analysis of when and how to use monoliths versus microservices, emphasizing purposeful architecture and the importance of architectural decisions aligned with business context.

Relation: Complements this work by recognizing that not every system needs microservices and that well-structured (modular) monoliths can be the most strategic choice. Vernon highlights the importance of Bounded Contexts and internal modularity, central concepts in our framework.

Reference: Vernon, V. (2021). Strategic Monoliths and Microservices: Driving Innovation Using Purposeful Architecture. Addison-Wesley Signature Series.

8.8 Enterprise Monorepo Angular Patterns (Nrwl/Nx Team)

Contribution: Practical patterns for organizing monorepos in enterprise environments, focusing on team scalability, code sharing, and dependency governance.

Relation: Validates in practice the monorepo modularization concepts presented in this work. Demonstrates how tools like Nx enable modular architectures in large-scale enterprise projects, with concrete examples of project structuring, libraries, and apps.

Reference: Nrwl. Enterprise Monorepo Angular Patterns. Nrwl Press.

8.9 Fundamentals of Software Architecture (Richards & Ford, 2020)

Contribution: Academic formalization of different architectural styles, including modular monoliths as a valid architectural pattern. Presents framework for analyzing trade-offs and architectural characteristics.

Relation: Provides theoretical basis for classifying modular architectures as a distinct architectural style. Validates the importance of characteristics like modularity, deployability, and testability that underpin our 10 principles.

Reference: Richards, M., & Ford, N. (2020). Fundamentals of Software Architecture: An Engineering Approach. O’Reilly Media.

8.10 Software Architecture: The Hard Parts (Ford, Richards et al., 2021)

Contribution: Deep analysis of trade-offs in modern distributed architectures, questioning when microservices really pay off and exploring alternatives like modular monoliths.

Relation: Complements this work by providing methodology for analyzing architectural trade-offs. Reinforces the message that architectural decisions should be based on context and real needs, not hype.

Reference: Ford, N., Richards, M., Sadalage, P., & Dehghani, Z. (2021). Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures. O’Reilly Media.

8.11 Building Evolutionary Architectures (Ford, Parsons, Kua, 2017)

Contribution: Concept of fitness functions and architectures that evolve incrementally over time, supporting constant change through engineering practices.

Relation: Directly substantiates the gradual evolution proposal of this framework. The concept of fitness functions can be applied to validate that modules maintain their boundaries (via enforce-module-boundaries) and that principles are respected over time.

Reference: Ford, N., Parsons, R., & Kua, P. (2017). Building Evolutionary Architectures: Support Constant Change. O’Reilly Media.

8.12 Team Topologies (Skelton & Pais, 2019)

Contribution: Framework for structuring technology teams based on workflow, defining patterns like stream-aligned teams, enabling teams, and platform teams.

Relation: Essential for the Organizational Governance section of this work. Modular architectures naturally align with stream-aligned teams, where each team owns one or more domain modules, with platform teams providing infrastructure modules (shared).

Reference: Skelton, M., & Pais, M. (2019). Team Topologies: Organizing Business and Technology Teams for Fast Flow. IT Revolution Press.

8.13 Monolith to Microservices (Newman, 2019)

Contribution: Pragmatic guide on monolith decomposition, recognizing that not all systems should become microservices and presenting patterns for incremental transition when necessary.

Relation: Validates the gradual evolution approach proposed in this framework. Newman demonstrates that transition should be conscious and incremental, exactly what modular architectures facilitate by establishing clear boundaries from the start.

Reference: Newman, S. (2019). Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith. O’Reilly Media.

8.14 Differentiation of This Work

This paper goes beyond by:

  1. Formalizing 10 principles as a cohesive framework
  2. Proposing implementation patterns empirically validated
  3. Including organizational governance as an integral part
  4. Establishing gradual evolution strategy between paradigms

9. Conclusion

9.1 Synthesis

Modular Architectures represent a β€œconscious middle ground” between traditional monoliths and microservices, grounded in:

9.2 Main Contributions

This work offers:

  1. Formal Framework with 10 Principles
    • Structural: Boundaries, Composability, Independence
    • Operational: State Isolation, Communication, Replaceability
    • Deployment: Deployment Independence, Individual Scale
    • Resilience: Observability, Fail Independence
  2. Validated Implementation Patterns
    • Module types (domain vs infrastructure)
    • Submodules for large domains
    • App design patterns (shared vs dedicated)
  3. Organizational Governance Model
    • Tech lead responsibilities
    • Fast Forward versioning
    • Management of operational challenges
  4. Gradual Evolution Strategy
    • 4 phases: Modular Monolith β†’ Clear Boundaries β†’ Dedicated Apps β†’ Microservices
    • Reversible decisions at each phase

9.3 Positioning

Modular Architectures are not revolution, but natural evolution of:

9.4 Future Work

Research directions:

  1. Quantitative Metrics of Cognitive Load
    • Formalize metrics to measure perceived complexity
    • Compare with monoliths and microservices
  2. Longitudinal Case Studies
    • Track evolution of real systems over years
    • Validate technical debt reduction hypothesis
  3. Validation in Different Ecosystems
    • Java, Go, .NET, Python
    • Identify universal vs language-specific patterns
  4. Automated Governance Tooling
    • Static analysis of principle violations
    • Automated refactoring suggestions

9.5 Openness

This work is an open contribution to the community. We encourage:


References

  1. Evans, E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional.

  2. Ghemawat, S., Grandl, R., Petrovic, S., Schwarzkopf, M., Whittaker, M., & McKinley, K. S. (2023). Towards Modern Development of Cloud Applications. In Proceedings of the 19th Workshop on Hot Topics in Operating Systems (HotOS β€˜23). ACM. https://doi.org/10.1145/3593856.3595909

  3. Khononov, V. (2023). Balancing Coupling in Software Design: Successful Software Architecture in General and Distributed Systems in Particular. Addison-Wesley Professional.

  4. Newman, S. (2021). Building Microservices: Designing Fine-Grained Systems (2nd ed.). O’Reilly Media.

  5. Nrwl. (2024). Nx: Smart Monorepos Β· Fast CI. Retrieved from https://nx.dev

  6. Mysliwiec, K. et al. (2024). NestJS: A progressive Node.js framework. Retrieved from https://nestjs.com

  7. Google. (2023). Service Weaver: A Framework for Writing Distributed Applications. Retrieved from https://serviceweaver.dev

  8. Spring. (2024). Modulith: A Modular Monolith Toolkit for Spring Boot. Retrieved from https://spring.io/projects/spring-modulith

  9. Fowler, M. (2014). Microservices: A Definition of This New Architectural Term. Retrieved from https://martinfowler.com/articles/microservices.html

  10. Vernon, V. (2013). Implementing Domain-Driven Design. Addison-Wesley Professional.

  11. Vernon, V. (2021). Strategic Monoliths and Microservices: Driving Innovation Using Purposeful Architecture. Addison-Wesley Signature Series.

  12. Nrwl. Enterprise Monorepo Angular Patterns. Nrwl Press.

  13. Richards, M., & Ford, N. (2020). Fundamentals of Software Architecture: An Engineering Approach. O’Reilly Media.

  14. Ford, N., Richards, M., Sadalage, P., & Dehghani, Z. (2021). Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures. O’Reilly Media.

  15. Ford, N., Parsons, R., & Kua, P. (2017). Building Evolutionary Architectures: Support Constant Change. O’Reilly Media.

  16. Skelton, M., & Pais, M. (2019). Team Topologies: Organizing Business and Technology Teams for Fast Flow. IT Revolution Press.

  17. Newman, S. (2019). Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith. O’Reilly Media.

  18. Richardson, C. (2018). Microservices Patterns: With Examples in Java. Manning Publications.


About the Authors

Waldemar Neto is co-founder of TechLeads.club and has experience in software architecture at companies like ThoughtWorks, Atlassian, and high-growth startups. Specialist in evolutionary systems and software engineering practices.

William Calderipe is co-founder of TechLeads.club with experience in enterprise development at Atlassian and other large-scale organizations. Focus on scalable architectures and DevOps practices.


Contact:

License: This work is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0).

You are free to:

Under the following terms:

More information: https://creativecommons.org/licenses/by/4.0/

How to cite this work:

Neto, W., & Calderipe, W. (2025). Modular Architectures: 10 Principles for 
Building Evolutionary Systems. TechLeads.club Whitepaper, v1.0.

Version: 1.0
Publication Date: October 13, 2025
Status: Initial Publication β€” Feedback Welcome