Is C4 component enough to represent your modules?
Challenges when modeling modules inside your deployment units — and how we solve it
As software architects, we all want to understand our systems — not only at the level of services and APIs, but inside each deployable unit.
We want to know how the modules relate, how behaviors are exposed, and how business concepts map to code. Yet the moment we try to model this, most existing approaches start to fail.
Many popular architecture models — including C4 and Spring Modulith — struggle to capture the true structure of modules inside deployment components.
They represent only a single level of modularity: a “component” in C4, or a “module” in Modulith.
That’s usually enough for a high-level overview, but it breaks down when you want to see what’s actually going on within a larger deployable unit — especially in systems that are close to modular monoliths.
However, an architecture model should contain such relations — such an ontology — that it can express all the mental models people hold about the structure of a deployment unit.
This becomes most visible when using C4 to model components inside a bigger codebase, like (modular) monoliths. At that moment you face a choice:
Should components represent groups of classes (similar to Modulith modules)?
Or should they represent individual classes, like in many C4 examples for smaller codebases?
Neither answer feels right, because real systems have multiple layers of modularity, coexisting within a single deployable unit. That’s why we designed a model that can represent them consistently.
The P3 Model — People, Domain, Technology
That’s why we propose the P3 model — composed of three perspectives: People, Domain, and Technology.
Modules belong to the domain perspective. What’s most important - the modules can be nested, and objects (classes) are represented separately. Behaviors (e.g. methods or functions) are also represented separately and can exist without objects, which allows us to map any elements of the functional architecture.
Behaviors can of course belong to domain objects as well. All behaviors can be assigned to business processes.
Elements from the domain perspective are connected with elements from the people perspective. Processes and modules can belong to teams and organizational units — and each behavior can be triggered by an actor, representing the users of the system.
We also have the technology perspective, where modules can be deployed inside deployment units and grouped into deployment containers. There is also a separate representation for APIs, which expose behaviors to the outside world.
What’s important - each element can have a tag that adapts the model to the language used in a given organization — for example, to its architectural style or internal patterns dialect. Tagging mechanism allows for example, to mark certain behaviors as system entry points and visualize them in a special way e.g . to show how deep is the logic behind an endpoint as described in this article.
Back to the domain perspective
This kind of model organization enables a consistent representation of different types of application and deployment architectures:
1) Modular Monolith

A single deployment unit with many modules. Modules may include optional submodules, and at the same time we can represent services, controllers, and repositories as separate domain objects contained within those modules.
When any of the modules contains a richer DDD model, we can also show it — as long as we decide to tag the relevant classes appropriately.
2) Microservices

We can have multiple deployment units from the technological perspective — each possibly containing just a single module. We can represent commands and events emitted by each service (as domain objects). The model allows us to track relations between events across services — to recognize that events used in different services are actually the same element — and to navigate from one service to another.
3) Hybrid architecture — microservices + monolith
This is where we have the most to offer, because this is the reality in many companies that are transitioning away from legacy systems.
They have a large monolith alongside emerging microservices.
In such cases, it is really hard to obtain a consistent model of the whole architecture using existing tools — and even harder to get a simple model that allows you to assess the complexity of each deployment unit at a glance.
P3 enables all of this by combining both of the approaches above.
Imagine a monolith composed of several modules, each containing its own objects (classes) — and next to it - microservices of various sizes: some with only a few key classes (e.g., services or handlers), others with more modules.
Within both the monolith and the microservices, we can track not only logical components like services and controllers, but also entities, events, and commands — the mechanisms of integration.
If we add to this the ability to filter what matters to us at a given moment, we gain enormous value — a view into the essential knowledge about the architecture that is relevant right now.
A short note on navigation
On top of these views, P3 also supports navigation between related elements.
You can move from a command to the event it emits, from that event to the handler in another service, then to the owning module, and further to the team or organizational unit responsible for it.
Our ambition is to make these relations automatically detected by the code scanner — so you can explore your architecture directly through these connections, without any manual configuration files or YAML definitions.
Summary
The goal of the P3 model is to provide a small set of simple abstractions — an ontology that allows modeling typical business software architectures in a universal way.
As mentioned above, one of the key features of the model is the possibility of visualization with filtering, which we have already demonstrated in earlier posts about our tool based on P3 — Noesis.vision.
In our near-term roadmap, we plan to extend it with automatic navigation via commands and events, allowing seamless traversal between modules, behaviors, and deployment units.
If this perspective resonates with you —
subscribe to this Substack ☝️
follow us on LinkedIn
or best of all — register for a free trial of our beta version (if you work with .NET).
The more of you join, the stronger our conviction (and that of our soon-to-be investors) that what we’re building truly makes sense.
Thank you!






