DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Protect Your Invariants!
  • Tactical Domain-Driven Design: Bringing Strategy to Code
  • Strategic Domain-Driven Design: The Forgotten Foundation of Great Software
  • Applying Domain-Driven Design With Enterprise Java: A Behavior-Driven Approach

Trending

  • The Third Culture: Blending Teams With Different Management Models
  • Zone-Free Angular: Unlocking High-Performance Change Detection With Signals and Modern Reactivity
  • Architecting Petabyte-Scale Hyperspectral Pipelines on AWS
  • Why Your QA Engineer Should Be the Most Stubborn Person on the Team
  1. DZone
  2. Coding
  3. Java
  4. Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible

Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible

Use CDI stereotypes + JMolecules annotations to make DDD architecture explicit, enforceable, and testable. This preserves design intent as your Java system evolves.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
Nov. 24, 25 · Analysis
Likes (4)
Comment
Save
Tweet
Share
5.6K Views

Join the DZone community and get the full member experience.

Join For Free

One subtle challenge in software architecture is that architectural thinking can feel detached from the codebase. We draw diagrams, define layers, identify responsibilities, and craft a coherent structure — yet the moment implementation begins, those architectural ideas fade into the background. Over time, systems drift not because developers ignore design, but because the code itself provides almost no way to express that design.

This tension is well documented. In Just Enough Software Architecture, George Fairbanks argues that programming languages lack constructs for directly representing architectural concepts. Java lets us model types, fields, methods, and packages, but offers no native way to encode ideas such as “presentation layer,” “domain logic,” “aggregate root,” or “infrastructure boundary.” Without these cues in the code, architecture becomes optional, verbal, and fragile.

To bridge this gap, we need a mechanism that brings architectural intent into the codebase while preserving Java's flexibility and neutrality. That mechanism is architectural evidence.

The Missing Language of Architecture

When architects talk about software structure, they rely on a specialized vocabulary: layers, domains, flows, policies, boundaries, services, aggregates. Yet none of these concepts are represented explicitly in the programming languages we use. The language of architecture exists only in documents, conversations, and diagrams.

This mismatch produces predictable symptoms:

  • A repository begins orchestrating domain rules
  • A use-case class starts manipulating HTTP objects
  • Entities become dependent on ORM-specific behaviors
  • Infrastructure code leaks into core business logic

These issues accumulate slowly, often unnoticed at first, because the code provides no warning when they cross architectural boundaries. The language itself does not help.

This is why architectural evidence matters: it fills the semantic gap between architectural intent and implementation reality.
But before understanding how to express architecture in Java, we need to explore why expressing architecture in code is strategically essential.

Why Architectural Evidence Matters

Architecture is not merely a design artifact; it is a constraint system that guides the structure and evolution of the code. However, constraints that exist only in documentation tend to be ignored or forgotten. Constraints expressed in conversations are even more vulnerable.

Architectural evidence solves this by:

  • Making architecture visible — so developers can immediately see the role of each component.
  • Preserving intent — so architectural decisions live in the codebase, not in transient documents.
  • Guiding decisions — so new contributors understand boundaries without guesswork.
  • Enabling governance — by giving tooling a way to detect violations automatically.
  • Providing longevity — ensuring the structure survives beyond team changes.

This is especially important in long-lived enterprise systems, where the architecture must outlast the individuals who created it.

Having established why architectural evidence is essential, the next question naturally follows:
Which architectural concepts should be made explicit?

Layers as First-Class Architectural Concepts

Most enterprise systems, regardless of style or technology, rely on a layered architecture. Fairbanks’ formulation is intentionally minimalistic: three layers that capture the essence of separation of concerns.

  1. Presentation layer: This is where interaction happens. It translates external input — HTTP requests, messages, or UI actions—into internal commands. Its purpose is communication and expression, not business logic.
  2. Domain layer: This is the intellectual center of the system: business rules, constraints, invariants, entities, aggregates, and domain policies. It represents knowledge rather than technology. Keeping this layer clean is the key to Domain-Driven Design (DDD).
  3. Support (Infrastructure) layer: This layer provides persistence, integration, messaging, and communication with external systems. It has no business semantics; instead, it offers technical capabilities.

Architects talk about these layers constantly, but the code rarely contains any explicit evidence of them. Packages give a hint, but nothing enforces or communicates a class’s architectural role.

This brings us to the practical value of architectural evidence: Annotations make these layers visible and enforceable. But before diving into how, a final conceptual step is needed: the role of testing.

Architecture as Something That Can Be Tested

Once architectural concepts become visible in code, they become testable. This transforms architecture from a document into an executable part of the system.

Tools like ArchUnit or ArchiJunit don’t invent architecture — they enforce the architecture that the team defines. With appropriate metadata, they can validate:

  • Layer independence
  • Domain purity
  • Absence of infrastructure dependencies in core logic
  • Restrictions on what repositories, controllers, and domain services can access

This is a profound shift. For the first time, architecture gains the same precision and verifiability as unit tests or API tests. Software no longer hopes to follow architecture; it proves it does.

With the conceptual groundwork in place — why architectural evidence matters, what architectural gaps it fills, and how layers shape the system — we are ready to explore the practical side: How these architectural concepts can be encoded directly in the codebase using annotations.

Show Me the Code

After understanding why architectural evidence matters, the next natural step is to see how these ideas come to life in actual code. The good news is that Jakarta EE — and specifically CDI — gives us all the pieces we need to elevate architectural concepts to first-class citizens in the codebase. Combined with JMolecules and test frameworks like ArchUnit, we can both express and validate architectural intent.

To make the example concrete, we’ll use a simple microservice in an e-commerce domain. The goal here is not to build a full application but to show how annotations become structural metadata that the architecture can rely on.

Setting Up the Environment

To start, you have two options for scaffolding the microservice:

Option 1: Create a Project Using Helidon’s MicroProfile Starter

Helidon starter:
https://helidon.io/starter/

Option 2: Use the Workshop Sample From GitHub

https://github.com/o-s-expert/ddd-workshop

Both approaches give you a clean Jakarta EE + MicroProfile base with CDI enabled.

Next, add JMolecules to your dependencies:

XML
 
<dependency>
    <groupId>org.jmolecules</groupId>
    <artifactId>jmolecules-ddd</artifactId>
    <version>${jmolecules.version}</version>
</dependency>

<dependency>
    <groupId>org.jmolecules</groupId>
    <artifactId>jmolecules-layered-architecture</artifactId>
    <version>${jmolecules.version}</version>
</dependency>


With the molecules available, we can now create our architectural annotations.

Defining the Architectural Annotations

Each annotation is built as a CDI stereotype combined with JMolecules semantics and your architectural layer markers. This makes the annotation an architectural artifact rather than a runtime-changing feature.

Application Service

The first annotation in this tutorial helps to define the use-case orchestrators inside the code, usually known as the application service.

While classic Evans-style DDD does not explicitly define “application services,” it is more common for modern layered architectures to treat them as a clean boundary between the presentation and domain layers. This helps us separate:

  • user interaction (presentation)
  • business rules (domain)
  • orchestration (application layer)
Java
 
@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
@ApplicationLayer
public @interface ApplicationService {
}


Domain Service

As the following annotation, we have the domain service, which expresses a business rule that doesn’t naturally belong to an entity or a value object.

This guarantees that any class annotated as a domain service remains part of the business logic rather than the infrastructure.

Java
 
@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
@Service
@DomainLayer
public @interface DomainService {
}


Repository

The third and final annotation we will cover today concerns implementing a repository. Naturally, we will use this annotation for implementation, not for the contract, since, as you know, the repository contract itself belongs to the domain.

Although repositories “touch” infrastructure, their concept belongs to the domain language. They sit at the boundary between domain logic and persistence technology.

Java
 
@Stereotype
@ApplicationScoped
@Inherited
@Retention(RUNTIME)
@Target(ElementType.TYPE)
@DomainLayer
@InfrastructureLayer
public @interface Repository {
}


With those three annotations, you are free to test and see the intention in your code directly; thus, you can see clearly the intention of a class. This does not go against the use of terminology, such as Sufixx, to identify those layers. Still, once the goal is the semantics of the business, those annotations can help identify and archive it clearly in the code.

Java
 
Validating Architecture with JMolecules + ArchUnit

Those annotations also help ensure code quality and automation. In our case, as a first step, we will use JMolecules' default behavior to verify whether the code follows the common practices of both DDD and layered applications. As you gain experience with both tools, you can increase the number of validations for this scenario.

public class JMoleculesDddUnitTest {

    @Test
    void checkTheLayerIntegration() {
        String packageName = SimpleGreetResource.class.getPackageName();
        JavaClasses classes = new ClassFileImporter().importPackages(packageName);
        JMoleculesArchitectureRules.ensureLayering().check(classes);
    }

    @Test
    void checkDDDIntegration() {
        String packageName = SimpleGreetResource.class.getPackageName();
        JavaClasses classes = new ClassFileImporter().importPackages(packageName);
        JMoleculesDddRules.all().check(classes);
    }
}


Validating Architecture With JMolecules + ArchUnit

Those annotations also help ensure code quality and automation. In our case, as a first step, we will use JMolecules' default behavior to verify whether the code follows the common practices of both DDD and layered applications. As you gain experience with both tools, you can increase the number of validations for this scenario.

Java
 
public class JMoleculesDddUnitTest {

    @Test
    void checkTheLayerIntegration() {
        String packageName = SimpleGreetResource.class.getPackageName();
        JavaClasses classes = new ClassFileImporter().importPackages(packageName);
        JMoleculesArchitectureRules.ensureLayering().check(classes);
    }

    @Test
    void checkDDDIntegration() {
        String packageName = SimpleGreetResource.class.getPackageName();
        JavaClasses classes = new ClassFileImporter().importPackages(packageName);
        JMoleculesDddRules.all().check(classes);
    }
}


Conclusion

This article demonstrated how architectural evidence transforms Domain-Driven Design from a conceptual approach into a practical, maintainable structure reflected in the code. By using CDI stereotypes, we improved code semantics and clarified design intent. When combined with JMolecules, this evidence is further strengthened, allowing seamless integration with ArchUnit to create tests and achieve scalable quality within your codebase.

Design Domain-driven design Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Protect Your Invariants!
  • Tactical Domain-Driven Design: Bringing Strategy to Code
  • Strategic Domain-Driven Design: The Forgotten Foundation of Great Software
  • Applying Domain-Driven Design With Enterprise Java: A Behavior-Driven Approach

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook