In the world of backend development and designing complex systems, it's not enough just to write good code — it's equally important to clearly communicate architectural ideas to your team, clients, or new developers joining the project. One of the most effective tools for this is the C4 Model — a simple yet powerful way to document and visualize software architecture.
Created by Simon Brown, the C4 model offers a hierarchical approach to diagramming, allowing you to gradually zoom in from broad concepts to detailed implementation. The model consists of four levels:
- System Context
- Containers
- Components
- Code
What makes it especially convenient is that C4 is often used together with PlantUML, a powerful text-based tool for creating UML diagrams. This allows you to describe your system structure directly in code, easily keep diagrams up to date, and automate their rendering.
Let’s explore each level and provide examples using PlantUML.
1. System Context Diagram
At the highest level of abstraction, we see the whole system within its environment. The goal of this level is to show:
- What system is being built
- Who interacts with it (users, external systems)
- How it connects to other systems
Example:
Suppose you're building a food delivery platform . The diagram would include: the core service itself, users (customers, couriers, restaurant owners, admins), payment gateways, SMS services, geolocation APIs, and analytics systems — everything your platform interacts with externally to function effectively.
PlantUML:
@startuml
title System Context Diagram: Food Delivery Service
skinparam rectangle {
roundCorner 20
}
actor "User" as user
actor "Courier" as courier
actor "Admin" as admin
actor "Restaurant Owner" as restaurant_owner
[Food Delivery Service] as foodservice
user --> foodservice : Orders food, views menu
courier --> foodservice : Receives tasks, updates status
restaurant_owner --> foodservice : Manages menu, accepts orders
admin --> foodservice : Administers system, analytics, moderation
foodservice --> [Payment Gateway] : Payment processing
foodservice --> [SMS Service] : Sends notifications
foodservice --> [Geolocation API] : Location detection
foodservice --> [Analytics System] : Sends metrics
note right of foodservice
A full-featured food delivery platform
supporting multiple restaurants, users,
couriers and analytics.
end note
@enduml
2. Container Diagram
At this level, we look inside the system and show its main containers — these are separate processes or hosts that communicate with each other. A container can be:
- A web application (e.g., Node.js API)
- A database (PostgreSQL, MongoDB)
- A message broker (RabbitMQ, Kafka)
- Microservices
- Frontend app (React)
Example:
In a food delivery platform, possible containers could be:
API Gateway — routes incoming requests to the appropriate services
Order Service — manages order lifecycle (creation, modification, queueing)
Restaurant Service — handles restaurant and menu data
User Service — manages user profiles and authentication
Notification Service — sends alerts and updates via SMS or push
Payment Service — processes payments through an external gateway
Location Service — integrates with geolocation APIs for couriers and users
Analytics Service — collects and analyzes system metrics
PostgreSQL Cluster — stores core data like users, orders, and restaurants
Redis Cache — improves performance by caching frequently accessed data
RabbitMQ — handles internal service communication via events
Kafka — logs all events for analytics and system recovery
PlantUML:
@startuml
title Container Diagram: Food Delivery Service
package "Food Delivery Service" {
[API Gateway] as gateway
[Order Service] as orderservice
[Restaurant Service] as restaurantservice
[User Service] as userservice
[Notification Service] as notificationservice
[Payment Service] as paymentservice
[Location Service] as locationservice
[Analytics Service] as analyticsservice
[PostgreSQL Cluster] as database
[Redis Cache] as cache
[RabbitMQ] as messagebroker
[Kafka] as eventlog
note bottom of orderservice
Manages order lifecycle:
creation, modification, queueing.
end note
note bottom of messagebroker
Used for internal communication
between services via events.
end note
note bottom of eventlog
Stores all system events
for analytics and recovery.
end note
}
gateway --> orderservice
gateway --> restaurantservice
gateway --> userservice
gateway --> paymentservice
gateway --> locationservice
orderservice --> database
orderservice --> messagebroker
orderservice --> cache
restaurantservice --> database
restaurantservice --> cache
userservice --> database
paymentservice --> [Payment Gateway]
locationservice --> [Geolocation API]
notificationservice <-- messagebroker
analyticsservice <-- eventlog
@enduml
3. Component Diagram
Now we go one level deeper and look at what components reside inside each container. At this point, developer-level design decisions begin to emerge:
- Which modules, services, repositories are used
- How they interact with each other
- Which frameworks and technologies are involved
Example:
Inside the Order Service container, there might be these components:
HTTP Handler — receives and routes incoming HTTP requests
Order Use Case — contains core business logic for creating, updating, and retrieving orders
Order Repository — interacts with the database to persist and retrieve order data
Event Publisher — publishes events (e.g., “OrderCreated”) to the message broker
Config Manager — provides configuration values to all components
Validator — ensures input data is valid before processing
Metrics Collector — gathers performance and usage metrics for monitoring
PlantUML:
@startuml
title Component Diagram: Order Service
package "OrderService" {
[HTTP Handler] as handler
[Order Use Case] as usecase
[Order Repository] as repo
[Event Publisher] as publisher
[Config Manager] as config
[Validator] as validator
[Metrics Collector] as metrics
note right of usecase
Contains business logic for creating,
updating and retrieving orders.
end note
note bottom of validator
Validates order data before
performing any operations.
end note
}
handler --> usecase : Create / Get order
usecase --> validator : Validates input data
usecase --> repo : Works with DB
usecase --> publisher : Publishes event
usecase --> metrics : Logs metrics
config -down-> handler
config -down-> usecase
config -down-> repo
config -down-> publisher
@enduml
4. Code Diagram
This is the lowest level — the source code level. Here you can show:
- Class diagrams
- Object interactions
- Method or function calls
However, code diagrams can quickly become outdated if not maintained. They’re best used to explain key algorithms or complex parts of the logic that are hard to understand without visual representation.
Example:
A diagram demonstrating the internal structure of the
OrderUsecase
, showing how it interacts with core components likeOrderRepository
,EventPublisher
, andValidator
. This UML diagram clarifies the flow of operations during order creation — from validation and persistence to event publishing, helping developers understand the logic and dependencies at the code level.
PlantUML:
@startuml
title Code Diagram: Order Usecase
class OrderUsecase {
+CreateOrder(*Order) error
+GetOrder(string) (*Order, error)
}
class OrderRepository {
+Save(*Order) error
+FindByID(string) (*Order, error)
}
class EventPublisher {
+Publish(event Event) error
}
class Validator {
+ValidateOrder(*Order) error
}
OrderUsecase --> OrderRepository : Uses
OrderUsecase --> EventPublisher : On create
OrderUsecase --> Validator : Before save
note right of OrderUsecase
Executes business logic for order creation,
calls repository and event publisher.
end note
@enduml
Why Use the C4 Model?
The C4 model is valuable because it:
- Simplifies communication between developers, analysts, testers, and managers.
- Allows you to see the big picture while having the ability to zoom into specific areas.
- Improves architecture documentation — especially important in long-term projects.
- Eases onboarding of new team members.
- Helps with risk assessment, threat modeling, and architectural reviews.
How to Start Using C4 and PlantUML
If you want to start applying C4 in your work, here are some steps:
- Start with the System Context level — define who interacts with what.
- Move to the Containers level — show the main technical blocks.
- Add the Components level for key containers — reveal their internal structure.
- Use the Code level when necessary — only where it adds real value.
- Write diagrams in PlantUML — this will help you maintain them easily.
- Integrate into documentation — for example, via Markdown or Confluence with PlantUML support.
Conclusion
The C4 model is more than just a set of diagrams — it's a structured approach to understanding and describing architecture. It helps everyone on the project — from technical specialists to business stakeholders — speak the same language.
If you're working on complex backend systems, microservices, or simply want to better organize your thoughts during design, the C4 model will be a great addition to your toolkit. And using PlantUML will make working with diagrams easy, repeatable, and well-documented.
📌 What do you think?
Have you tried C4 or PlantUML yet?
What tools or methods do you use to document your system architecture?
👇 Leave a comment below — I’d love to hear your experience!
👍 Don’t forget to like and share if you found this helpful — so more people can benefit from it too!
📣 Follow me and read my content on other platforms:
Check out this and other articles on my pages:
🔗 LinkedIn
📚 Medium
📘 Facebook
✈️ Telegram
🐦 X
Oh, almost forgot — my personal website.
🔔 Follow me not to miss new articles and guides on hot topics!