Skip to content

AtriumX Core – Architecture & Rationale

AtriumX exists because the current ILS landscape often presents a choice between two distinct approaches:

  • Traditional Systems (e.g., Koha): Functionally comprehensive and battle-tested, but their monolithic architecture and long history can make it challenging for teams to adopt modern, cloud-native development workflows.
  • Large-Scale Modular Systems (e.g., FOLIO): Architecturally ambitious and highly flexible, but the microservices-first approach (incorporating technologies like Kafka and Okapi) can introduce substantial operational complexity and infrastructure costs.

AtriumX explicitly targets the missing middle:

A modern, opinionated, developer‑friendly ILS core that is simple to run, hard to misuse, and easy to extend.


To maintain a lean and focused codebase, AtriumX does not aim to:

  • Be a microservice-heavy ecosystem
  • Support every library workflow on day one
  • Inherit traditional architecture constraints (such as MARC-centricity in the core or tightly coupled database logic)
  • Optimize for “any stack” contributors

AtriumX optimizes for:

  • Correctness over convenience
  • Explicit architecture over magic
  • Long‑term maintainability over short‑term speed

AtriumX follows a Modular Monolith with Hexagonal (Ports & Adapters) architecture.

┌─────────────────────────────┐
│ Frontend UI │
│ (Vue / React, TypeScript) │
└──────────────▲──────────────┘
│ JSON / REST
┌───────────────┴───────────────┐
│ API Adapters │
│ (Spring MVC, SIP2, etc.) │
└───────────────▲───────────────┘
│ Ports
┌───────────────────────┴────────────────────────┐
│ Core │
│ Domain + Application Services (Pure Kotlin) │
│ │
│ Catalog • Patrons • Circulation • Auth (later)│
└───────────────▲───────────────────▲────────────┘
│ │
Outbound Ports Outbound Ports
│ │
┌───────────────┴───────┐ ┌───────┴───────────┐
│ Persistence Adapter │ │ Search Adapter │
│ (PostgreSQL + jOOQ) │ │ (OpenSearch/ES) │
└───────────────────────┘ └───────────────────┘

Key idea:

All business rules live in the Core. Everything else is replaceable.


Single repository, multiple Gradle modules:

atriumx-core/
├── app/ # Spring Boot runtime (The Glue)
│ ├── build.gradle.kts # Depends on :core and :adapters
│ └── src/main/.../AtriumXApp.kt
├── core/ # The Domain (Pure Kotlin)
│ ├── build.gradle.kts # Depends on NOTHING (standard libs only)
│ └── src/main/kotlin/org/atriumx/core/
│ ├── catalog/
│ │ ├── Item.kt # Data Class
│ │ ├── ItemRepository.kt # Interface (Port)
│ │ └── CatalogService.kt # Business Logic
│ ├── circulation/
│ └── shared/ # Common Exceptions / Value Objects
└── adapters/
├── api-rest/ # The Input Port
│ ├── build.gradle.kts # Depends on :core
│ └── src/main/.../catalog/
│ ├── ItemController.kt
│ └── dto/
├── persistence-jooq/ # The Output Port
│ ├── build.gradle.kts # Depends on :core, postgres, jooq
│ └── src/main/.../catalog/
│ └── JooqItemRepository.kt # Implements core.ItemRepository
└── search-opensearch/ # Secondary Output Port
core → depends on NOTHING
adapters → depend on core
app → depends on adapters

Reverse dependencies are forbidden.

This is enforced by:

  • Module boundaries
  • Gradle dependencies
  • Code reviews

  • Aggregates (e.g. Loan, Item, Patron)
  • Domain invariants (loan rules, limits, states)
  • Application services (use‑cases)
  • Domain events
  • Interfaces (ports) for external concerns
  • Spring annotations
  • HTTP concepts
  • SQL
  • JSON
  • Authentication frameworks

The Core must:

  • Compile without Spring
  • Be unit‑testable without mocks of frameworks
  • Remain stable even if adapters change

6. Adapters Layer (Delivery & Infrastructure)

Section titled “6. Adapters Layer (Delivery & Infrastructure)”

Responsibilities:

  • Translate HTTP → application commands
  • Validate request DTOs
  • Map domain errors → HTTP responses

Characteristics:

  • Uses Spring MVC
  • Imports Spring Boot BOM
  • Contains no business logic

Controllers are thin by design.


Persistence Adapter (adapters/persistence-jooq)

Section titled “Persistence Adapter (adapters/persistence-jooq)”

Responsibilities:

  • Implement repository ports
  • Map domain objects ↔ relational schema

Why jOOQ (not JPA):

  • Schema‑first
  • Compile‑time SQL safety
  • No hidden lazy loading
  • Predictable performance

This is a deliberate choice to prioritize explicit control over ORM-driven abstractions.


Search Adapter (adapters/search-opensearch)

Section titled “Search Adapter (adapters/search-opensearch)”

Responsibilities:

  • Consume domain events
  • Maintain search indexes

Search is:

  • Eventually consistent
  • Explicitly decoupled from core logic

No domain rule ever depends on search results.


The app module:

  • Is the only Spring Boot application
  • Owns:
    • Embedded server
    • Bean wiring
    • Configuration

Why isolate it:

  • Prevent Spring leakage into core
  • Keep startup concerns localized
  • Allow alternative runtimes later

Frontend is a separate project:

  • TypeScript + Vue or React
  • Communicates only via REST
  • No shared code, no server‑side rendering assumptions

The backend must be fully usable via:

  • Postman
  • curl
  • API clients

UI is a consumer, not a co‑author.


  • Catalog
  • Patrons
  • Circulation
  • REST only
  • Authentication & Authorization
  • SIP2
  • Roles & policies
  • Acquisitions
  • Serials (low priority, high complexity)

This approach:

  • Keeps up with the modern design principles
  • Avoids operational overhead
  • Tries to embrace the cloud-native practices
  • Attracts the right contributors for the stack
  • Keeps the system understandable after 5-10 years

It is boring on purpose.


If a developer cannot explain where a piece of code belongs, it doesn’t belong.

AtriumX intentionally trades short-term velocity for long-term architectural stability.