SuperAPI AI Directory

modular-monolith

Coding & development

View repository

Updated 10-15 20:31

About this skill

Modular monolith

One process, multiple domain boundaries. Consolidates the software: same transaction when needed, extraction to a service later once the module exists. Microservices: microservices-architecture. Contexts: ddd. Arrows: clean-architecture.

When (not) to use

Use with one team or a few teams, one pipeline, a domain that is still unstable, or frequent data coupling.

Do not use as an excuse for a global common folder. It is not a “messy monolith with namespaces”.

Module = context

apps/api/                 # host (composição)
  modules/
    catalog/              # público: catalogapi
    ordering/
    billing/
  shared/                 # kernel mínimo (ids, clock) — não o domínio
  • Each module: code + schema (prefixed tables or SQL schema) + tests.
  • Dependency only via the module's public API (orderingapi.PlaceOrder), never the neighbor's internal / package db .
  • Cycles forbidden. If A needs B and B needs A: event, ACL, or the boundary is wrong (ddd).

Data

  • Writes: each module owns its tables. No cross-module FK (or FK only for what is a stable and deliberate contract).
  • Reads from another context: the owner's API/query, materialized view, or eventual copy — not a silent JOIN in the global ORM.
  • Transaction cross-module in the same DB: documented exception (first step), not the default. Prefer eventual consistency — event-driven-architecture.

Vertical slice

Inside the module, organize by feature, not by the app's global layer:

ordering/
  placeorder/
    handler.go | endpoint
    service.go
    sql.go
  getorder/
    ...
  • A slice can talk to the own module's domain. It does not import the billing.
  • Clean Layers adapter inside the slice if the slice grows; do not force 4 projects per POST.

Host

  • One main: registers modules, middleware, config.
  • Authn in the host; authz in the module that owns the resource.
  • Feature flags to hide an incomplete module — not if module == nil scattered around.

Extraction

Signal to become a service: different deploy cycle, different scale, owning team, isolatable failure.

Checklist: public API is already stable, data is copyable, events already exist, contracts tested. Then microservices-architecture.

Anti-patterns

  • shared/domain with all entities
  • One AppDbContext / PrismaClient with 80 models used by everyone
  • Modules by layer (module-controllers, module-repos)
  • Microservice extracted in the first PR “to get it right from the start”
  • Internal import “only in this file”

Integration with other skills

  • Decision: software-architecture
  • Contexts: ddd
  • Dependencies: clean-architecture
  • Events between modules: event-driven-architecture
  • After the split: microservices-architecture
  • Host HTTP: api-design

Completion criteria

  • Modules with a public API and owned data
  • No import cycle
  • Features in slice (or layers within the module)
  • Imaginable extraction path without rewriting the domain
  • Host only composition