Make the right architecture choice. Covers when to use monolith, microservices, or modular monolith with decision criteria, trade-offs, migration strategies, and team size considerations.
This guide helps teams decide between microservices and monolithic architecture when building software systems. It focuses on concrete architectural decisions, configuration options, and failure modes that arise in practice. The choice impacts deployment models, data consistency, service boundaries, and operational complexity.
Use bounded contexts from domain-driven design to define service boundaries:
-- Example: User management service schema
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Example: Order processing service schema
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
total DECIMAL(10,2),
status VARCHAR(50)
);
Use REST over HTTP for service-to-service communication:
# Example: Call another service using curl
curl -X GET \
http://order-service:8080/api/v1/orders/123 \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
-H 'Content-Type: application/json'
Set up service discovery using environment variables or configuration files:
# Example: Kubernetes service definition
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 8080
targetPort: 8080
Use eventual consistency patterns instead of distributed transactions:
// Example: Event-driven approach using message queues
func ProcessOrder(orderID string) error {
// Publish order created event
err := publishEvent("order.created", orderID)
if err != nil {
return fmt.Errorf("failed to publish event: %w", err)
}
// Update local state
return updateOrderStatus(orderID, "processing")
}
Use a single repository for all application logic:
# Example: Git repository structure
git clone https://example.com/myapp.git
cd myapp
ls -la
# Output:
# app/
# main.go
# handlers/
# user.go
# order.go
# config/
# config.go
Use shared database for related functionality:
-- Example: Shared schema in monolith
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE user_profiles (
user_id UUID REFERENCES users(id),
bio TEXT,
avatar_url VARCHAR(500)
);
Use feature flags to toggle functionality without redeployment:
# Example: Feature flag configuration
features:
new_ui: true
experimental_api: false
Microservices require independent deployment:
# Example: Deploying a single service
kubectl set image deployment/user-service user-service=registry.example.com/user-service:v1.2.3
Monoliths can be deployed as a unit:
# Example: Deploying monolith
docker build -t myapp:latest .
docker run --rm -p 8080:8080 myapp:latest
Microservices require monitoring per service:
# Example: Check service health
curl http://user-service:8080/health
# Response:
# {"status":"healthy","timestamp":"2023-01-01T00:00:00Z"}
Monoliths are monitored as a single unit:
# Example: Check monolith health
curl http://myapp:8080/health
# Response:
# {"status":"healthy","timestamp":"2023-01-01T00:00:00Z"}
Microservices need circuit breaker patterns:
// Example: Circuit breaker implementation
type CircuitBreaker struct {
state string
failureRate float64
failures int
}
func (cb *CircuitBreaker) Call(fn func() error) error {
if cb.state == "open" {
return fmt.Errorf("circuit breaker is open")
}
err := fn()
if err != nil {
cb.failures++
if cb.failures > 5 {
cb.state = "open"
}
return err
}
cb.failures = 0
cb.state = "closed"
return nil
}
For microservices, each service owns its data:
-- Example: User service database schema
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Example: Order service database schema
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
total DECIMAL(10,2),
status VARCHAR(50)
);
For monoliths, shared database is acceptable:
-- Example: Shared schema in monolith
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
total DECIMAL(10,2),
status VARCHAR(50)
);
Use event sourcing or change data capture for consistency:
# Example: Kafka consumer setup
kafka-console-consumer.sh \
--bootstrap-server kafka.example.com:9092 \
--topic user-updated \
--from-beginning
Microservices use asynchronous communication:
// Example: Asynchronous message publishing
func SendNotification(userID string, message string) error {
payload := map[string]interface{}{
"user_id": userID,
"message": message,
"timestamp": time.Now(),
}
return publishToQueue("notifications", payload)
}
Monoliths use synchronous calls:
// Example: Synchronous internal call
func ProcessPayment(orderID string) error {
user, err := GetUser(orderID)
if err != nil {
return fmt.Errorf("failed to get user: %w", err)
}
// Direct function call within monolith
return chargeUser(user.ID, orderID)
}
Microservices use environment variables:
# Example: Set service configuration
export DATABASE_URL="postgresql://user:pass@db:5432/myapp"
export SERVICE_PORT="8080"
Monoliths may use configuration files:
# Example: Configuration file for monolith
database:
url: "postgresql://user:pass@db:5432/myapp"
pool_size: 10
server:
port: 8080
Services may fall out of sync without clear error messages:
# Example: Missing data consistency check
curl http://order-service/api/v1/orders/123
# Response:
# {"id":"123","status":"processing"}
# But user-service doesn't know about this order yet
Multiple services increase deployment complexity:
# Example: Deployment failure due to missing dependency
kubectl apply -f service.yaml
# Error message:
# Error from server (BadRequest): error when creating "service.yaml":
# Service "user-service" is not ready
Network calls introduce latency:
# Example: High latency due to network calls
time curl http://order-service/api/v1/orders/123
# real 0m0.543s
# user 0m0.001s
# sys 0m0.002s
Microservices require distributed tracing:
# Example: Tracing request across services
curl -H "X-Trace-ID: abc123" http://frontend-service/api/v1/users/123
# Without proper tracing, this becomes opaque
Small teams (1-5 developers) often benefit from monoliths:
# Example: Monolith startup script
./start.sh
# Output:
# Starting monolith application...
# Listening on port 8080
# Database connected successfully
Large teams (>10 developers) may benefit from microservices:
# Example: Service composition
kubectl get pods -l app=user-service
# NAME READY STATUS RESTARTS AGE
# user-service-7b5b6c9d8-fg4j5 1/1 Running 0 5m
# user-service-7b5b6c9d8-kj2h3 1/1 Running 0 5m
If services share data heavily, monolith may be better:
-- Example: Complex join across services
SELECT u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed';
If data is mostly isolated, microservices are preferred:
-- Example: Isolated service queries
SELECT * FROM users WHERE id = '123';
SELECT * FROM orders WHERE user_id = '123';
Microservices require more operational overhead:
# Example: Service health monitoring
kubectl get pods -A | grep -E "(user|order)-service"
# NAMESPACE NAME READY STATUS RESTARTS AGE
# default user-service-7b5b6c9d8-fg4j5 1/1 Running 0 5m
# default order-service-7b5b6c9d8-kj2h3 1/1 Running 0 5m
Microservices may require careful dependency management:
# Example: Service readiness check
kubectl rollout status deployment/user-service
# Waiting for deployment "user-service" rollout to finish: 0 of 1 updated replicas are available...
# deployment "user-service" successfully rolled out
Choose synchronous communication for monoliths, asynchronous for microservices.
Decide whether services should own their data or share databases.
Consider how often and how you deploy changes.
Evaluate the complexity of monitoring distributed systems.
Match architecture to team size and organizational structure.
This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy