# DomainOS-ddd-starter **Repository Path**: young169/DomainOS-ddd-starter ## Basic Information - **Project Name**: DomainOS-ddd-starter - **Description**: No description available - **Primary Language**: Java - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-26 - **Last Updated**: 2026-08-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DomainOS DomainOS is a Spring Boot starter that gives enterprise R&D teams a development framework built on the patterns of tactical domain-driven design in Java. It provides a suitable set of [Spring](https://spring.io/) libraries complemented by scaffolding through [Hygen](https://github.com/jondot/hygen) templates allowing to quickly get started with aggregates, repositories, domain events etc. in a consistent and minimal fashion. The project lays a foundation to build the application in a modular fashion based on [Spring Modulith](https://spring.io/projects/spring-modulith). Layering along the simplified onion architecture cleanly separates domain, application, and infrastructure concerns, helping to reduce complexity as applications grow large. Both DDD and architectural concepts are made explicit and enforced through [jMolecules](https://github.com/xmolecules/jmolecules). ## Getting started 1) Once you checked out the repository from Git, import it into your IDE 2) Adapt artifact group and name, description, developer info in the `pom.xml` to your liking 3) Right-click on the `pom.xml` and import the project as Maven project to your IDE 4) Refactor the package `com.example.app` to a suitable name 5) Replace the path `com/example/app` in all files to match the package you defined in the previous step 6) Also update the package name in the `pom.xml`, where it appears in the NullAway compiler option (`-XepOpt:NullAway:AnnotatedPackages=com.example.app`) — otherwise the null-safety checks silently stop applying to your code 7) Rename the value of the variable "RootPackage" under _templates/variables.ejs to your chosen package name ### Register byte-buddy in IntelliJ jMolecules equips aggregates with required JPA annotations by executing the `byte-buddy` Maven plugin. To make sure that these annotations are also available when editing and running code from your IDE, you have to register the plugin to your `build` and `rebuild` commands. For IntelliJ, this is done as follows: 1) After opening the project in IntelliJ, right click on pom.xml and select 'Add as Maven Project'. 2) Open the Maven tool window (found on the right) and find the list of plugins included in the project. Expand `byte-buddy`, right click on transform-extended, and select 'Execute After Build' and 'Execute After Rebuild'. ### Add the jMolecules plugin and facets to IntelliJ If you use IntelliJ as your IDE, it is recommended to install the jMolecules Plugin, which reduces the number of warnings shown otherwise, because IntelliJ does not understand the jMolecules annotations. You can then go to Project Settings, and add the jddd-core and jddd-event facet under "Facets". ### Install hygen for scaffolding The project contains templates to scaffold elements of tactical domain-driven design such as aggregates, repositories, or domain events in a consistent fashion. To make use of this tool, install NodeJS and then run the following command to install `hygen`. `npm i -g hygen` In the folder `_templates`, replace the package `com/example/app` in all `*.java.t` files with your own package name. ## Scaffolding your DDD artifacts The scaffolding promotes a naming strategy where the name of a repository is simply the plural form of the aggregate, e.g. `Orders` for aggregate `Order`. This underlines the aim to employ a purely domain oriented language in the domain ring of the application. The scaffolding implies a package design where top-level packages form modules of the applications, containing directly the domain logic. Controllers, on the other hand, which belong to the infrastructure ring of the onion architecture, are placed in a subpackage `web` close to the domain classes. The approach has been described in this [blog post](https://medium.com/elca-it/feature-based-modular-code-organization-in-java-e4b611d6c103). To create a new feature module, type this command: > $ hygen feature new todo Next, create an aggregate root with a repository and some first commands and events by typing: > $ hygen aggregate new Todo --feature=todo If you want to make your aggregate available through the REST API, type: > $ hygen controller new Todo --feature=todo Reference (master) data — entities with their own lifecycle that aggregates copy relevant fields from rather than associate with (see [Master data handling](#master-data-handling)) — has its own generator: > $ hygen referencedata new Country ## Running Tests Run unit tests: > $ mvn test Run integration tests: > $ mvn verify Run hygen template integration tests. This is useful to ensure that changed hygen templates still work correctly after changing it to your needs. Generates a throwaway `world/Human` feature and a `Country` reference data entity via hygen, wires the latter into the former as its citizenship, and tests the full aggregate lifecycle through the REST API. Rename both in the `hygen-it` profile if your own project uses either name, so that the cleanup below cannot touch your code. The generated code is then put through a nested `mvn test`, which is what compiles and runs the *generated* unit tests (`HumanTest`, `HumansTest`, `CountriesTest`) and lets ArchUnit and Modulith analyse the generated main code — the outer build's `test` phase runs before hygen has produced anything. That doubles the test run, which is why this lives in an opt-in profile. Executes a git clean of the /src folder afterwards, on the failure path too, and discards `target/classes` and `target/test-classes` so that a following build cannot inherit generated classes or a duplicate Flyway migration. Test execution will be aborted if there are uncommitted changes in /src. > $ mvn verify -Phygen-it ## Continuous Integration The project includes a GitHub Actions workflow (`.github/workflows/build.yml`) that runs the build and tests on every push and pull request. ## Baked-in concepts This project follows an opinionated approach to building DDD-style applications in Java. It is highly inspired by Oliver Drotbohm's [Spring Restbucks](https://github.com/odrotbohm/spring-restbucks) sample application. The approach implemented here more strictly separates different rings of the onion architecture, avoiding in the domain package dependencies to Jackson and other concepts related to the REST API, and explicitly introducing a package for application and infrastructure rings of the onion. ### Package structure The generator adheres to the following package structure, which is largely governed by the name of features and of aggregates. Feature packages are kept on top-level to profit from Spring modulith defaults and to keep the package hierarchy flat. To still clearly separate them from application and infrastructure, the latter two use the unconventional prefix "_":
[root-package]/
  _application/                         Application ring of the application
  _infrastructure/                      Infrastructure ring of the application
    [api1]/                               Implementation of an API to another system
    [api2]/                               Implementation of another API to another system
    ...
    logging/                              Logging configuration 
    persistence/                          Persistence configuration
    security/                             Security configuration
    web/                                  Global REST API configuration
  common/                               Root package for common functionality
    logging/                              Helpers to work with log prefixes
    model/                                Basic domain types (Command, AbstractAggregate, exceptions, ...)
    web/                                  Shared web helpers (SecuredAggregateCommands, ProjectionLinks, ...)
    events/                               Helpers to work with domain events (@RetryableApplicationModuleListener)
  [feature1]/                           Root package of a feature
    [Aggregate1].java                     An aggregate, here with name "Sample"
    [Aggregate1]Command.java              Commands to change the aggregate
    [Aggregate1]Event.java                Domain events produced by the aggregate
    [Aggregate1s].java                    Repository to work with the aggregate
    web/                                  Root package of the feature's REST API
      [Aggregate1]OperationsController.java Controller exposing operations of the aggregate
      [Aggregate1]Summary.java              Projection with reduced data suitable to render lists
      [Aggregate1]Detail.java               Projection with detailed data to render detail views
      [Aggregate1]Links.java                Factory for HAL links of the aggregate
      [Aggregate1]ApiConfiguration.java     Link generation for the aggregate's projections
  [feature2]/                            Root package of another feature
    ...
### Persistence Aggregates are directly persisted to a relational database through JPA / Hibernate. To avoid jeopardizing the domain model with persistence logic, Aggregates rely on jMolecules `byte-buddy` plugin to generated required annotations. The one exception is their base class `AbstractAggregate`, which carries the `@MappedSuperclass` and version field that optimistic locking needs, so that no aggregate has to. The initial schema is created through Flyway. Schema changes follow a **migration-per-change** convention: the initial schema lives in `V0001__initial_schema.sql`, and every subsequent change is a *new* versioned file, never an edit to an already-applied one. Accordingly, the `hygen aggregate`/`hygen referencedata` scaffolding emits a fresh `V__create_.sql` migration rather than appending to `V0001`. Because `spring.jpa.hibernate.ddl-auto` is `validate` (see below), a migration that does not match the mapped entity fails the build at startup. #### Value-object-aware column naming Single- and multi-valued value objects are embedded directly into the owning aggregate's table, without any `@Column` or `@AttributeOverride` annotations on the domain model. This is handled centrally by `ValueObjectAwareImplicitNamingStrategy` (registered via `spring.jpa.hibernate.naming.implicit-strategy`): - **Single-value wrappers** collapse to the owning attribute, dropping the wrapper component. A wrapper is recognized by the `Value` naming convention of its sole component, so no list of field names has to be maintained: - `SampleId(UUID uuidValue)` mapped as `id` → column `id` - `Principal(String stringValue)` mapped as `principal` → column `principal` - **Multi-field value objects** keep the owning-attribute prefix, which disambiguates sibling embeddables: - `I18nText(String en, String de)` mapped as `name` → columns `name_en`, `name_de` - nested: `City(int postalCode, I18nText name)` mapped as `city` → `city_postal_code`, `city_name_en`, `city_name_de` - **`@ElementCollection` elements** live in their own table, so the collection prefix is dropped and columns are named by the path within the element. As a result, aggregates carry no persistence annotations: the schema follows from the value objects' shape. The `domainRingShouldNotNameSchemaObjects` rule in `ArchitectureTests` keeps it that way — it fails the build if a type in the domain ring names a table or column (`@Table`, `@Column`, `@JoinColumn`, `@AttributeOverride`, ...), in that package or any below it. The usual temptation is a SQL keyword collision such as an `order` attribute; solve that by overriding Spring Boot's default physical naming strategy (`CamelCaseToUnderscoresNamingStrategy`, which is what snake-cases the names the implicit strategy above produces), by enabling `hibernate.auto_quote_keyword`, or in `META-INF/orm.xml` — not by annotating the model. #### Enum mapping Enums are persisted by name to match the `VARCHAR` schema columns, and that is declared **once for the whole model** by `_infrastructure/persistence/StringEnumTypeContributor`: a new enum needs no mapping code at all. Do **not** fall back to `@Enumerated`: the default ordinal mapping breaks as soon as enum constants are reordered, and annotating the domain model would reintroduce exactly the persistence details the byte-buddy approach keeps out of it — an ArchUnit rule in `ArchitectureTests` fails the build on any `@Enumerated` usage. Hibernate has no global "enums as string" switch (`hibernate.type.prefer_native_enum_types` only picks a *native* enum SQL type once the style is already `STRING`), which is why this goes through the `TypeContributor` SPI: it runs before any entity is bound, and the `EnumJavaType` it registers is what `InferredBasicValueResolver` then asks for the recommended JDBC type. Setting the style later — from an `AdditionalMappingContributor`, say — is silently ignored, because entity binding has already resolved the `BasicValue` by then. Registration is via `META-INF/services`, so it also applies in the narrowed context of an `@ApplicationModuleTest`, which never scans that package for beans. #### Master data handling Master data (e.g. a list of cities) is modeled as a jMolecules `@Entity` under `referencedata`, not as a full-blown aggregate. Such reference data has its own lifecycle and identity but is never referenced by association from another aggregate. Instead, an aggregate can embed relevant fields of masterdata in a child value object. The starter project illustrates this idea on the example of a list of cities: - `referencedata.City` is the master-data entity (own table, own id). - `sample.City` is a value object — a copy of the relevant master-data fields, embedded into the `Sample` aggregate (`City.of(referenceData)` performs the copy). This keeps the aggregate self-contained and immutable against later changes to the master data: a `Sample` records the city as it was at the time of creation, rather than following a live reference. The copy is embedded into the `sample` table via the naming strategy above (`city_postal_code`, `city_name_en`, `city_name_de`), again without any column annotations. The master-data repository is exported as a REST resource (`@RepositoryRestResource`), so the frontend can list and select reference data (e.g. `GET /api/cities`). It is read-only for clients: `WebConfiguration` disables the write methods of every exported repository. An aggregate is changed through the operations of its root, and the generic CRUD API of a reference-data repository would sit outside the `@Secured` annotations that authorize those. A command then references the chosen entity **by its URI**: the `CreateSample` command declares `City city`, and the request passes a link, which Spring Data REST resolves to the entity before the command is handled: ```json POST /api/samples { "name": { "en": "Sample 1", "de": "DE_Sample 1" }, "owner": "/api/people/{personId}", "city": "/api/cities/{cityId}" } ``` The aggregate then copies the resolved `City` into its embedded value object. The same URI-resolution mechanism binds the `owner` link to a `Person` aggregate, from which the `Sample` denormalizes the owner name (see [Loosely coupled modules](#loosely-coupled-modules-with-domain-events)). ### REST API with Links in HAL format The REST API is largely provided out of the box by Spring Data REST. Its converters allow to reference aggregates through their URI. If exposed through `@RepositoryRestResource`, finder methods of a repository are published under the search resource of an aggregate collection. Through configuration, http methods to create, update or patch an aggregate are disabled in favor of using well-defined operations for creating and changing aggregates, forcing them to go through the business logic implemented in the domain layer. #### Serialization of value objects Domain identifiers and single-attribute value objects carry no Jackson annotations. `jmolecules-jackson` serializes any jMolecules `Identifier` or single-field `@ValueObject` to — and deserializes it from — its bare wrapped value. For example `SampleId(UUID uuidValue)` appears in JSON simply as the UUID string, and a command field of that type is bound straight from the bare value. This keeps the domain model free of serialization concerns (no `@JsonValue`/`@JsonCreator`), matching the same annotation-free approach used for persistence. #### Summary and detail projections It is a common scenario that an application displays a list of aggregates, with a detail view of a single aggregate if the user clicks on an item in the list. Both use cases, list view and detail view, have a different needs in terms of data required to display the respective view. The list view typically only needs the name of the aggregate and some additional fields such as the status of each aggregate. The detail view, on the other hand, typically requires all fields. To display an editor which allows to edit an aggregate instance, even more data such as values for dropdowns would be desirable. To make the interaction of the GUI with the backend efficient, Spring allows defining _projections_ of an aggregate to return different representations depending on the use case.The hygen _controller_ generator produces two projection interfaces, `Summary` and `Detail`. It is then up to the developer to equip each with the required getters. Please note that projections are a concept of Spring Data and therefore have an impact how data is fetched from the database. See the documentation [here](https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html). A projection can be fetched by appending a `projection` query parameter in the call to the aggregate resource, e.g. ```bash GET /api/samples?projection=summary ``` Spring allows adding arbitrary data to a projection through the use of Spring's `@Value` annotation and a Spring Expression (SpEL). The `@` prefix accesses any bean in the Spring context, making projections really powerful: in the following example, the `detail` projection resolves the `owner` association to the full `Person` aggregate through the `people` repository bean, so the detail representation embeds the entire person: ```java @Projection(name = "detail", types = {Sample.class}) public interface SampleDetail { String getName(); String getDescription(); @Value("#{@people.resolveRequired(target.owner)}") Person getOwner(); } ``` The summary projection, in contrast, stays cheap: it exposes `getOwnerName()`, which reads the owner's name straight off the aggregate, where it is kept as a denormalized copy (see [Loosely coupled modules](#loosely-coupled-modules-with-domain-events)) — no cross-aggregate resolution at read time. In sum, projections act as highly customizable data transfer objects, seamlessly integrated with the application's REST API. #### HAL link generation Spring MVC and Spring Data REST are based on the hypertext application language [HAL](https://www.ietf.org/archive/id/draft-kelly-json-hal-11.html). It basically allows to attach hyperlinks to the representation of a REST resource. If you do a GET request to the API root, Spring returns an object with the field "_links", pointing at the aggregates served by the application: ```bash $ curl -u "user:1234" localhost:8080/api ``` ```json { "_links" : { "cities" : { "href" : "http://localhost:8080/api/cities" }, "samples" : { "href" : "http://localhost:8080/api/samples{?projection}" }, "profile" : { "href" : "http://localhost:8080/api/profile" } } } ``` When accessing a single aggregate, Spring shows the fields of the aggregate, plus two default links: ```json { "name" : "Sample 1", "description" : "Description of sample 1", "state" : "DRAFT", "_links" : { "self" : { "href" : "http://localhost:8080/api/samples/c30cb331-5aee-4e00-99d0-17b9141ee5c1" }, "sample" : { "href" : "http://localhost:8080/api/samples/c30cb331-5aee-4e00-99d0-17b9141ee5c1" } } } ``` We can now use the same mechanism to communicate to the client of the API which business operations (i.e. commands) are currently available depending on the aggregate state and the user role. The convention is to produce a HAL link for each available operation and let it point at the path served by the controller to execute the command. The logic to produce the links is placed in a class `Links` in the web API package of the feature. For the sample aggregate, this looks like this: ```java @Component @RequiredArgsConstructor public class SampleLinks implements RepresentationModelProcessor> { private final EntityLinks entityLinks; private final SecuredAggregateCommands aggregateCommands = new SecuredAggregateCommands<>(Sample.class, SampleCommand.class, SampleOperationsController.class); @Override public EntityModel process(EntityModel model) { if (model.getContent() instanceof Sample sample) { aggregateCommands.getAllowedCommands().forEach( command -> addCommandLink(model, sample, command)); model.addIf(!model.hasLink(IanaLinkRelations.SELF), () -> entityLinks.linkForItemResource(Sample.class, sample.getId()).withSelfRel()); } return model; } private void addCommandLink( EntityModel model, Sample sample, Class commandType) { val rel = aggregateCommands.getRel(commandType); model.addIf(sample.can(commandType), () -> entityLinks .linkForItemResource(Sample.class, sample.getId()).slash(rel) .withRel(rel)); } } ``` A command link appears only when **both** conditions hold: the aggregate permits the operation in its current state (`sample.can(commandType)`) **and** the current user is authorized to invoke it. The authorization part is handled by `SecuredAggregateCommands` (in `common.web`): at construction it reads, for each command, the `@Secured` role declared on the `SampleOperationsController` method that handles it, and `getAllowedCommands()` then returns only the commands whose required role the authenticated user holds. Because the controller's `@Secured` annotation stays the single source of truth, link visibility and endpoint authorization cannot drift apart — Spring HATEOAS does not derive link visibility from Spring Security on its own. `SecuredAggregateCommands` composes the plain `AggregateCommands`, which resolves the command relations, keeping the role logic separate from the relation metadata. Because only `@Secured` feeds this mechanism, an ArchUnit rule in `ArchitectureTests` forbids any other security annotation (`@PreAuthorize`, `@RolesAllowed`, ...) on operations controllers: such an annotation would still be enforced when the operation is invoked, but the link layer could not see it — the link would be offered to every user and the call would then be rejected. Projections of the aggregate (see above) do not automatically pick up the links generated for the aggregate. The `ProjectionLinks` helper class can be used to declare a link generator for the projection which delegates to the link generator for the aggregate: ```java @Configuration public class SampleApiConfiguration { @Bean ProjectionLinks sampleSummaryLinks(SampleLinks delegate) { return new ProjectionLinks<>(delegate, Sample.class); } } ``` With this, projections have now exactly the same links as the raw aggregate. ### Loosely coupled modules with domain events Feature modules stay loosely coupled by three general concepts: - **Reference by identity.** An aggregate references an aggregate of another module only through a jMolecules `Association`, never through a direct object reference. Modules therefore never share object graphs, and each module can load, persist and evolve its aggregates independently. - **Denormalize what you need to read.** Identity-only references mean a module cannot read data owned by another module on demand. Where such data is needed for display or logic, the aggregate keeps a **denormalized copy** of just the required fields, populated when the aggregate is created. - **Synchronize through domain events.** A denormalized copy must be kept in sync when its source changes. Rather than one module calling into the other (which would couple them), the owning module publishes a domain event and the dependent module reacts to it with a Spring Modulith `@ApplicationModuleListener`, updating its copy through a regular command. The listener is transactional and asynchronous: it runs in its own transaction after the source change has committed, and Spring Modulith's event publication registry (the `event_publication` table) persists each event until its listener completes — no update is lost if the listener fails or the application restarts mid-flight. These concepts are illustrated in the sample feature as follows; read it for adoption in your own feature modules: 1. `Sample` holds an `Association` to its owner and keeps a denormalized copy of the owner's name in its `ownerName` field, populated at creation time from the resolved owner. 2. `Person.updateName(...)` publishes a `PersonUpdated` event. 3. A Spring Modulith `@ApplicationModuleListener` (`SampleOwnerNameSynchronizer`, in `_application`) reacts to it, looks up the affected samples via `Samples.findByOwner(...)`, and executes the internal `UpdateOwnerName` command on each of them (`sample.updateOwnerName(UpdateOwnerName.of(newName))`) — like every state change, the sync goes through a command. 4. `updateOwnerName` updates the field and, if it actually changed, emits a `SampleOwnerNameChanged` event of its own. The command is internal: the operations controller declares no handler for it, so it never appears as a HAL link. The choreography is verified end to end with Spring Modulith's `Scenario` API in `SampleOwnerNameSyncScenarioTest`: it publishes a `PersonUpdated` event and waits for the resulting `SampleOwnerNameChanged` to arrive. This is the counterpart to the SpEL-based lookup shown for projections above: denormalizing the value onto the aggregate keeps reads simple and self-contained, at the cost of the event-driven synchronization described here. ### Architecture verification The setup comes with several architecture verifications which ensure that new code does not violate the architecture. #### Module Architecture The module architecture is verified through Spring Modulith. It ensures that modules do not form cycles and that only allowed dependencies are present. #### Onion architecture The Onion architecture is verified through jMolecules, using the simplified onion architecture as a basis, having the three rings * domain * application * infrastructure Dependencies are only allowed in this direction: infrastructure -> application -> domain #### DDD architecture jMolecules also ensures that the elements of tactical domain-driven design are used correctly. This is enforced twice: the `ArchitectureTests` run the jMolecules ArchUnit rules (`JMoleculesDddRules` and the simplified onion rules) as part of `mvn verify`, and the jMolecules annotation processor (`jmolecules-apt`, wired into the `default-compile` annotation-processor path) detects breaches already at compile time. A project-specific rule additionally enforces the command pattern itself: every public state-changing method on an aggregate root must take its command as the only parameter — accessors, the `can(...)` guard and `Object` methods are exempt. A setter or a loose parameter list fails the build. ### Nullability The project uses [JSpecify](https://jspecify.dev/) annotations for nullability. Package-level `@NullMarked` annotations (one per package, guarded by the nullability rules in `ArchitectureTests`) declare that all types are non-null by default; use `@Nullable` to explicitly opt a type out. This contract is enforced at build time by [NullAway](https://github.com/uber/NullAway), which runs as an [Error Prone](https://errorprone.info/) plugin while the main sources compile. Any potential null-pointer dereference — a `@Nullable` value dereferenced without a check, a `@NonNull` field left uninitialized, a `@Nullable` value returned from or passed where `@NonNull` is required, and so on — **fails the build**. This turns the nullness contract from documentation into a guarantee, which is especially valuable when new contributors or AI coding agents extend the code. The check is wired into the `maven-compiler-plugin`'s `default-compile` execution: - `error_prone_core` and `nullaway` are added to the annotation-processor path (alongside Lombok); - `-Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=com.example.app` runs NullAway — and only NullAway — as an error; - the compiler is forked with the `jdk.compiler` `--add-exports`/`--add-opens` that Error Prone needs on current JDKs. Only the main sources are checked; test compilation keeps just Lombok, so test idioms (mocks, autowired fields, fluent assertions) don't have to satisfy the analyzer. Adjust the enforced scope via `AnnotatedPackages`, or drop `-XepDisableAllChecks` to additionally enable Error Prone's own bug-pattern checks. ### Validation Command parameters use Jakarta Bean Validation (`@Valid`, `@NotNull`) for input validation, ensuring domain invariants are checked at the API boundary. ### Domain Exception Handling Domain exceptions extending `DomainException` are automatically mapped to appropriate HTTP responses by `DomainExceptionHandler`. This provides consistent error handling for domain rule violations. ### Code formatting Formatting is a build gate, not a matter of taste: [Spotless](https://github.com/diffplug/spotless) with the Eclipse JDT formatter (profile in `eclipse-formatter.xml`) runs `spotless:check` in the `verify` phase and fails the build on unformatted code. Run `mvn spotless:apply` to reformat (and after generating code from the templates, since the generated output is not pre-formatted). The profile is configured to **never join wrapped lines**: intentional line breaks — the project convention is to break before stream operations and before builder operations — survive formatting instead of being folded back into one line by an opinionated formatter. #### Using the profile in your IDE `eclipse-formatter.xml` is a native Eclipse formatter profile, so IDEs can pick it up: - **Eclipse**: import it directly under Preferences → Java → Code Style → Formatter → Import. - **IntelliJ**: import it as a code-style scheme (Settings → Editor → Code Style → Java → ⚙ → Import Scheme → Eclipse XML Profile). IntelliJ maps the settings onto its own formatting engine, which is close but not identical; for an exact match install the *Adapter for Eclipse Code Formatter* plugin, which formats with the real Eclipse engine. Either way, the build gate is the authority: if the IDE and Spotless ever disagree, `mvn spotless:apply` wins. ## Support for AI coding agents The repository declares a generic INSTRUCTIONS.md which can be fed to AI coding agents such as Github Copilot, Cursor, Claude Code, etc. The folder .claude/ contains further instructions specific for Claude Code. The AI is instructed to bootstrap new feature packages, aggregates, and REST APIs using the scaffolding with hygen, allowing you to establish a sound basis with baked in coding conventions with respect to naming, package layout and library use. ### AI-assisted commits Commits in this repository may be AI-assisted; such commits carry a `Co-Authored-By: Claude ...` trailer. Every change is human-reviewed and must pass the full build gate — NullAway null-safety, the jMolecules/ArchUnit architecture rules, Spring Modulith module verification, the formatting check, and the test suite — before it is accepted. Responsibility for every change remains with the human author; the trailer discloses tool usage, it does not shift accountability. ## Open Issues * Add documentation to generated artifacts * Allow adding operations with related commands and events one by one ## References * https://odrotbohm.github.io/2021/04/Spring-RESTBucks-in-2021/ * https://github.com/odrotbohm/spring-restbucks ## License MIT