Design modular monolith architectures. Use when structuring applications with clear module boundaries, avoiding premature microservice decomposition, or enforcing separation of concerns within a single deployable unit.
shared/ or common/ package with zero domain logicapp/
├── modules/
│ ├── payment/
│ │ ├── api.go # Public interface
│ │ ├── service.go # Business logic
│ │ ├── repository.go # Data access
│ │ ├── models.go # Domain models
│ │ └── events.go # Domain events
│ ├── billing/
│ │ ├── api.go
│ │ ├── service.go
│ │ └── ...
│ └── notification/
│ └── ...
├── shared/
│ ├── database.go
│ ├── logger.go
│ └── http.go
└── main.go
// payment/api.go — the ONLY exported interface
type PaymentService interface {
ProcessPayment(ctx context.Context, req PaymentRequest) (PaymentResult, error)
GetPaymentStatus(ctx context.Context, id string) (PaymentStatus, error)
}
// payment/events.go
type PaymentCompletedEvent struct {
PaymentID string
Amount int64
Timestamp time.Time
}
// billing/service.go — subscribes, never imports payment internals
func (s *BillingService) OnPaymentCompleted(event PaymentCompletedEvent) error {
return s.createInvoice(event.PaymentID, event.Amount)
}
-- Each module owns its schema
CREATE SCHEMA payment;
CREATE TABLE payment.transactions (...);
CREATE SCHEMA billing;
CREATE TABLE billing.invoices (...);
-- NO cross-schema joins in application code