PROPELOO

MONOLITH TO MICROSERVICES / DECOMPOSITION

Extract microservices from your monolith — only when the problem requires it.

PROPELOO engineers monolith-to-microservices migrations — from domain analysis and service boundary identification through strangler fig extraction, inter-service communication design and the operational infrastructure that makes a distributed system maintainable. We start every engagement by asking whether microservices are actually justified.

Microservices solve specific problems. A monolith that does not have those problems does not need to become microservices.

The pattern of "we should break this monolith into microservices" without a specific justification is one of the most expensive architectural mistakes in modern software development. Microservices solve: independent deployment velocity when different teams need different release cadences, independent scaling when specific components have different load profiles, and technology heterogeneity when specific services genuinely need different stacks. If your monolith does not have these problems, microservices will give you all the operational complexity of a distributed system without the benefits. PROPELOO starts with the question: what specific problem do microservices solve for your team and your system? The answer shapes whether we recommend extraction, modular monolith refactoring, or leaving the architecture alone.

The decomposition process.

System Layers

  • Domain Analysis Layer: Bounded context identification, service boundary definition, data ownership mapping
  • Extraction Layer: Strangler fig service extraction, anti-corruption layer, data separation
  • Communication Layer: Synchronous API design (REST/gRPC), async events (Kafka/SQS), service contracts
  • Data Layer: Database per service, shared data elimination, saga patterns for distributed transactions
  • Operations Layer: Service deployment, distributed tracing, service mesh, operational runbooks

Core Technical Capabilities

  • Domain-driven Decomposition

    Event Storming workshop to identify bounded contexts, aggregate roots and domain events. Service boundaries drawn at bounded context boundaries — not technical boundaries (no "database service" or "notification service").

  • Strangler Fig Extraction

    Proxy routes new functionality to extracted service while monolith handles existing functionality. Gradual traffic migration. Each extracted service is independent before the monolith's equivalent is decommissioned.

  • Anti-corruption Layer

    Translation layer between monolith and extracted services — insulates each service from the monolith's domain model. Allows services to use clean domain models without inheriting monolith complexity.

  • Data Separation

    Database per service achieved via: separate schema within shared DB (easier), then separate DB per service (full isolation). Shared tables identified and ownership assigned. Cross-service data access via API, not direct DB query.

  • Distributed Transactions

    Choreography saga (event-driven compensation) for services that previously shared a database transaction. Saga orchestration with Temporal for complex multi-step workflows that must be durable.

  • Service Contracts

    Consumer-driven contract testing with Pact — each service publishes its API contract, consumers test against it. Prevents breaking changes in distributed systems where API compatibility cannot be enforced by the compiler.

How we think about monolith decomposition.

Extract one service, validate the operational model, then extract the next. Never extract ten services simultaneously — the complexity compounds faster than the teams can handle.

  • Modular monolith first, extraction second

    A monolith with clear module boundaries — enforced by package access controls, not just conventions — provides most of the benefits of microservices (team autonomy within modules, clear service contracts) without the operational overhead. Refactor the monolith into a well-modularised system before extracting services. The first services to extract will be obvious.

    Axiom:

  • Service boundaries must align with data ownership

    A service that reads data from another service's database is not a microservice — it is a distributed monolith with worse failure characteristics. The hardest part of service extraction is separating the data. A service owns its data exclusively. Other services access that data via the service's API. Plan the data separation before starting the code separation.

    Axiom:

  • Operations must be ready before extraction starts

    Extracting services into a team without distributed tracing, service health monitoring and deployment automation creates operational chaos. The infrastructure for running distributed services — CI/CD per service, distributed tracing, service registry, Kubernetes or ECS deployment — must exist before the first service is extracted.

    Axiom:

  • Extract the right services first

    Good first extraction candidates: services that need to scale independently (the payment service gets 100x traffic on Black Friday), services owned by a specific team that is blocked by the monolith deployment cycle, and services that use different technology than the monolith (ML prediction service in Python, core in Java). Bad first candidates: services tightly coupled to the core data model, services that would require distributed transactions immediately.

    Axiom:

Decomposition strategy decisions.

  • Microservices or modular monolith?

    Impact: Modular monolith is often the correct target. It provides clean architecture and team autonomy without distributed system overhead. Extract actual microservices only when independent deployment velocity or independent scaling is a demonstrated need.

    • Microservices — independent deployment, independent scaling, distributed system complexity
    • Modular monolith — clean boundaries, single deployment, lower ops burden
    • Mini-services (3-8 services) — pragmatic middle ground
    • Keep current monolith — valid if no specific problem to solve
  • What to extract first?

    Impact: Notification service is a classic first extraction — it has no dependencies on other services, its boundary is clear and failure is gracefully degradable. Auth service is more complex due to performance requirements.

    • Auth service — used by everything, self-contained, clear boundary
    • Notification service — truly decoupled, good starter
    • Domain with clearest boundaries — lowest coupling, easiest extraction
    • The pain point — where monolith is causing most friction
  • Synchronous vs asynchronous service communication?

    Impact: REST/gRPC for synchronous queries (need immediate response). Events (Kafka/SQS) for commands (fire and forget, eventual consistency acceptable). Do not use events just because they are fashionable.

    • REST everywhere — simple, tight coupling, latency stacks
    • Events everywhere — loose coupling, eventual consistency complexity
    • REST for queries, events for commands — standard hybrid
    • gRPC internally — typed, fast, Protobuf schemas
  • Database per service strategy?

    Impact: Separate schema in shared DB during extraction phase (faster, lower risk), separate DB per service after services are stable and the data boundary is validated.

    • Separate schema, shared DB immediately — lower ops overhead, partial isolation
    • Separate DB per service immediately — full isolation, higher ops overhead
    • Shared DB during extraction, separate later — pragmatic sequencing
    • Shared data patterns (read replicas) — some services read shared data
  • API gateway?

    Impact: API gateway for external-facing traffic. Service mesh for internal service-to-service authentication and observability. Both for mature distributed systems.

    • Kong/API Gateway — centralised routing, auth, rate limiting
    • Service mesh (Istio) — infrastructure-level, transparent
    • Client-side service discovery — each client knows all services
    • BFF (Backend-for-Frontend) — separate aggregation API per client
  • Distributed transaction handling?

    Impact: Design service boundaries to avoid distributed transactions first. When unavoidable: Temporal for orchestrated sagas — durable, observable, handles failures correctly.

    • Saga (choreography) — event-driven compensation, harder to trace
    • Saga (orchestration) — central coordinator, easier to understand
    • Avoid distributed transactions via boundary design
    • Two-phase commit — avoid in microservices

What PROPELOO decomposes.

  • E-commerce Monolith Decomposition

    Extract order service, payment service, inventory service and notification service from monolith — with event-driven communication and eventual consistency.

  • SaaS Platform Decomposition

    Extract billing, user management and domain services from SaaS monolith — enabling independent deployment by separate teams.

  • Modular Monolith Refactoring

    Refactor unstructured monolith into well-modularised codebase with enforced boundaries — without the operational overhead of microservices.

  • Auth Service Extraction

    Extract authentication and authorisation from monolith into dedicated service — with JWT, OAuth 2.0, RBAC and multi-tenancy.

  • API Gateway + Service Mesh

    Design and implement API gateway (Kong) and service mesh (Istio) for existing microservices — centralised auth, rate limiting, mTLS, distributed tracing.

  • Architecture Assessment

    Evaluate current monolith architecture, identify service boundaries via DDD, assess microservices readiness and produce extraction roadmap.

The decomposition stack.

  • Design Tools

    Stack: Event Storming (Miro), Domain Storytelling, C4 Model diagrams, Bounded context canvas

  • Communication

    Stack: REST + OpenAPI, gRPC + Protobuf, Kafka (events), AWS SQS/SNS, NATS

  • Contract Testing

    Stack: Pact (consumer-driven), Spring Cloud Contract, OpenAPI validation

  • Distributed Transactions

    Stack: Temporal.io, AWS Step Functions, Choreography (Kafka events), Saga libraries

  • Operations

    Stack: Kubernetes, Istio / Linkerd, ArgoCD, OpenTelemetry, Jaeger

  • API Gateway

    Stack: Kong, AWS API Gateway, Traefik, Nginx

Microservices expand the attack surface.

  • Service-to-service authentication

    Services must authenticate each other — not rely on network isolation alone. mTLS via service mesh or JWT service tokens with short expiry. A compromised service should not have unauthenticated access to all other services.

  • API gateway security

    Authentication, authorisation and rate limiting at the gateway before requests reach individual services. Services behind the gateway trust the gateway's authentication assertions.

  • Secrets per service

    Each service has its own database credentials, API keys and certificates. Shared credentials between services violate least privilege. HashiCorp Vault with dynamic secrets for database credentials.

  • Data access boundary enforcement

    Services must never directly query another service's database. All cross-service data access via API. Enforce via network policy and database credentials scoping.

  • Distributed tracing for security

    Security events in distributed systems need correlation across service boundaries. Correlation IDs propagated through all service calls. Security monitoring that can reconstruct the full request chain.

  • Container and Kubernetes hardening

    Same Kubernetes security requirements as standalone K8s: Pod Security Standards, network policies, RBAC, Secrets encryption, image scanning.

From monolith to services — safely.

  1. 01. Domain Analysis

    Event Storming workshop, bounded context identification, service candidate evaluation, extraction roadmap.

  2. 02. Operational Foundation

    CI/CD per service, Kubernetes deployment, distributed tracing, service health dashboards.

  3. 03. First Service Extraction

    Extract lowest-risk service via strangler fig, validate operational model, confirm monitoring.

  4. 04. Data Separation

    Separate schema/database for extracted service, eliminate cross-boundary DB access.

  5. 05. Inter-service Communication

    REST/gRPC for synchronous, events for async, contract tests for API compatibility.

  6. 06. Iterate

    Extract services per roadmap priority — each following the same validated process.

  7. 07. Monolith Decommission

    Decommission monolith components as services take over — never all at once.

Frequently Asked Questions

Should we actually use microservices?

Ask: does your team need independent deployment velocity (different teams blocked by the same deployment pipeline)? Does a specific component need to scale independently? Do different components genuinely need different technology stacks? If the answers are mostly no, a modular monolith provides most of the benefits with significantly less operational overhead. Microservices are not a default architecture — they are a solution to specific organisational and scaling problems.

How do we identify service boundaries?

Event Storming is the most effective technique: workshop with domain experts and engineers mapping domain events (things that happen in the system), commands (things that trigger events) and aggregates (clusters of data that change together). Bounded contexts emerge naturally — groups of related concepts with a consistent ubiquitous language. Service boundaries drawn at bounded context boundaries are stable because they reflect domain reality.

What is the Strangler Fig pattern?

New functionality goes into extracted services. A proxy (API gateway or load balancer) routes requests — some to the monolith, some to the new services. As more functionality is extracted, the monolith handles less traffic. Eventually the monolith handles nothing and is decommissioned. The key: each service is independently validated before the monolith equivalent is removed. No big-bang cutover.

How do we handle database decomposition?

Database decomposition is the hardest part of microservices extraction. Start by identifying which tables are owned by which bounded context (if a table is accessed by multiple contexts, that is a boundary problem to resolve first). Extract service-specific schemas within the shared database initially (lower risk). Migrate to separate database per service after the service is stable and the data boundary is clean. Never skip this step — shared databases create implicit coupling.