jMolecules Layered Architecture

Mar 06, 2026 · 5 min read

software-architecture layered-architecture jmolecules annotations separation-of-concerns dependency-rules java kotlin guide intermediate

Contents

Overview

Layered Architecture separates software into four conceptual layers: Interface, Application, Domain, and Infrastructure. Each layer has a specific responsibility and clear dependency rules. jMolecules provides annotations that help you mark classes or packages as belonging to one of these layers, ensuring the right abstractions, boundaries, and dependencies are in place.

jMolecules Layered Architecture — Four layers with strict dependency rules

In a typical layered approach:

Dependency Rules

Each layer has strict rules about which other layers it can and cannot depend on. These rules prevent circular dependencies and keep each layer focused on its role.

Layer Dependency Matrix — Which layers can depend on which

Must Depend On

Specifies which layers a given layer is allowed to rely on. Enforces a logical flow from higher-level layers (like Interface) down to the Domain or to cross-cutting Infrastructure components where appropriate.

Must Not Depend On

Specifies which layers a given layer cannot rely on. Prevents circular dependencies and preserves a clean architecture.

jMolecules Annotations and Their Constraints

Annotation Must Depend On Must Not Depend On Represents
@InterfaceLayer @ApplicationLayer @DomainLayer, @InfrastructureLayer External API / UI — handles incoming requests (web, CLI, etc.)
@ApplicationLayer @DomainLayer @InterfaceLayer, @InfrastructureLayer* Business Orchestration — coordinates flows between Domain and other subsystems
@DomainLayer None @InterfaceLayer, @ApplicationLayer, @InfrastructureLayer Core Business Logic — Entities, Value Objects, Aggregates, Domain Services
@InfrastructureLayer @DomainLayer, @ApplicationLayer @InterfaceLayer Technical Services — Persistence, Messaging, external integrations
*In many implementations, the Application Layer may indirectly use Infrastructure via interfaces or repositories, but it should not directly invoke infrastructure details. Usually, the Infrastructure Layer provides implementations of interfaces that the Application Layer consumes.

Example: User Registration System

User Registration Flow — How a POST /users request flows through all four layers

Let's illustrate these rules with a simple "User Registration" scenario:

Domain Layer (User Entity)

@DomainLayer
data class User(
    val id: String? = null,
    val name: String,
    val email: String
)

Application Layer (UserService)

@ApplicationLayer
class UserService(private val userRepository: UserRepository) {

    fun registerUser(name: String, email: String): String {
        // Business logic orchestration
        val user = User(name = name, email = email)
        return userRepository.save(user)
    }
}

Interface Layer (UserController)

@InterfaceLayer
@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {

    @PostMapping
    fun registerUser(@RequestBody userDto: UserDto): ResponseEntity<String> {
        val userId = userService.registerUser(userDto.name, userDto.email)
        return ResponseEntity.ok(userId)
    }
}

data class UserDto(val name: String, val email: String)

Infrastructure Layer (UserRepository + Persistence)

// The Application Layer references this interface, but not the direct CrudRepository
interface UserRepository {
    fun save(user: User): String
    fun findById(id: String): User?
}

@InfrastructureLayer
@Repository
interface CrudUserRepository : CrudRepository<User, String>

@InfrastructureLayer
class UserRepositoryImpl(
    private val crudUserRepository: CrudUserRepository
) : UserRepository {

    override fun save(user: User): String {
        return crudUserRepository.save(user).id!!
    }

    override fun findById(id: String): User? {
        return crudUserRepository.findById(id).orElse(null)
    }
}

Dependency Rules in Action

UserController (@InterfaceLayer)

UserService (@ApplicationLayer)

User + model classes (@DomainLayer)

UserRepositoryImpl (@InfrastructureLayer)

Why These Rules Matter

Why Layered Architecture Matters — Key benefits

Separation of Concerns

Each layer has a clear role. Changes in one layer (e.g., UI or persistence) don't cascade throughout the system.

Testability

Domain and Application layers can be tested in isolation by mocking Infrastructure or Interface layers.

Maintainability

Well-defined boundaries prevent accidental coupling and make the codebase easier to comprehend and evolve.

Technology Independence

Swap technologies (e.g., JPA to MongoDB) by only changing the Infrastructure layer, without affecting Domain or Application logic.

Conclusion

Using the jMolecules annotations for Layered Architecture ensures each layer remains focused on its core responsibilities:

By adhering to the "must depend on" and "must not depend on" constraints, you achieve a system that is modular, testable, and more robust against changing technical requirements.