Releases: poppycoderr/domain-driven-kit
Release list
v0.5.0
One operator context for cross-cutting persistence concerns: audit fields and fail-closed multi-tenancy in the MyBatis starter, set per request by the web starter. ArchGuard fix hints are now available in English.
Added
OperatorandOperatorContextinddk-core(com.ddk.core.context): who is acting and for which tenant, withrunAs/callAsthat restore the previous value andwrapto carry the operator to another thread- The web starter sets
OperatorContextfor each request when the application declares anOperatorResolverbean - The MyBatis starter fills
createByandupdateByfrom the operator (StringorLongfields), next tocreateTimeandupdateTime - Multi-tenancy in the MyBatis starter (
ddk.mybatis.tenant.enabled=true): every statement gets the tenant condition fromOperatorContext, and a statement without a tenant fails instead of reading all tenants;ignore-tablesand MyBatis-Plus's@InterceptorIgnoreare the exemptions - ArchGuard fix hints are available in English and Chinese. The language follows the JVM's default locale and can be set with the system property
ddk.archguard.language(enorzh)
Changed
- The reasons attached to the rules in
CommonArchRules(thebecauseclauses shown in reports) are now in English
v0.4.1
Fixed
- The ArchGuard report named the wrong class for a violation inside a constructor or static initializer: it reported the class being depended on instead of the class that contains the violation
v0.4.0
Middleware integrations with domain semantics: aggregate locks, duplicate-submit protection and rate limits on Redisson, scheduled jobs that run on one instance, the error contract in OpenAPI docs, Flyway migrations in generated projects, and ddk-test for domain assertions and container presets.
Added
ddk-concurrency-starter:@AggregateLockholds a Redisson lock per aggregate instance around the method's transaction,@Idempotentrejects a request key that was already submitted, andAggregateLocksis the programmatic entry point@RateLimitinddk-concurrency-startergives each key, such as a user or tenant, a budget of calls per period shared by all instances; over the limit it throwsRateLimitedException(RATE_LIMITED), which the web starter maps to 429AggregateBusyException(AGGREGATE_BUSY) andDuplicateRequestException(DUPLICATE_REQUEST) inddk-core; the web starter maps both to 409 Conflict- The web starter documents the error contract when springdoc is on the classpath: every operation gets 400 / 409 / 500 responses with the failed
ApiResponsebody, andcomponents.schemas.ErrorCodelistsCommonErrorplus everyErrorCodeenum in the application's packages with its message.ddk.web.openapi=falseturns it off. The BOM manages springdoc 3.1; the user example serves Swagger UI ddk-test:DdkAssertionsfor aggregates (hasRaisedExactly,hasRaised,hasRaisedNoEvents) and rejected operations (assertThatRejected(...).withCode(...)), andDdkContainerspresets for Redis, MySQL and RocketMQ. Generated projects and the example depend on it with test scopeddk-job-starter: turns on@Scheduledand wires ShedLock with a Redis lock, so a job marked@SchedulerLockruns on one instance at a time; defaults underddk.job.lock.*. The optional ruleCommonArchRules.SCHEDULED_JOBS_MUST_BE_LOCKEDfails the build when a@Scheduledmethod has no@SchedulerLockCommonArchRules.SCHEDULED_JOBS_MUST_RESIDE_IN_ADAPTERkeeps@Scheduledand@XxlJobmethods in the adapter layer, so scheduled jobs reach the domain only through application services; generated projects and the example check it
Changed
- Generated projects and the user example manage their schema with Flyway (
spring-boot-starter-flyway, scripts insrc/main/resources/db/migration) instead ofschema.sql; the generatedAGENTS.mdand skills tell agents to add a new migration per schema change
v0.3.0
Reliable domain events: a transactional outbox on Spring Modulith's event publication registry, delivery to Kafka, RocketMQ, AMQP and JMS, event contracts, and idempotent consumers. The repository contract now reports optimistic-lock conflicts.
Added
@IntegrationEventincom.ddk.core.domainmarks domain events for delivery outside the process and names their target and message key, without framework annotations in the domain layerddk-event-starterhands@IntegrationEventevents to Spring Modulith's event externalization when it is on the classpath, so they are recorded in the business transaction and delivered after commit; the BOM manages Spring Modulith 2.1@IntegrationEvent(id = ...)puts the event's own identifier into theddk-event-idmessage header, so it stays stable across resubmissions@IntegrationEvent(type, version)and theddk-event-type/ddk-event-versionheaders give each integration event a stable contract name and version- The event starter verifies
@IntegrationEventdeclarations at startup (accessors, version, duplicate contracts) instead of failing at delivery time - The event starter delivers
@IntegrationEventevents to RocketMQ whenrocketmq-clientis on the classpath and aDefaultMQProducerexists orddk.event.rocketmq.name-serveris set:topic:tagtargets, per-key queue ordering, contract headers as user properties; the BOM manages the client version IdempotentConsumer(ddk.event.inbox.enabled=true) runs a handler once per consumer and message ID, registering the message in the handler's transaction behind a savepointConcurrentUpdateException(CONCURRENT_UPDATE), which the web starter maps to 409 ConflictIdentifierJacksonModulewrites typed identifiers such asUserIdas their raw value and reads them back through the subclass'sof(...)factory; the web and event starters register it. Without it Jackson wrote identifiers as{}, so a serialized domain event lost its IDsGenericRepositoryImplaccepts typed identifiers such asUserIdand unwraps them to key values; the example and archetype skills useGenericRepository<User, UserId>
Fixed
GenericRepositoryImpl.updatesilently ignored an optimistic-lock conflict or a removed row and still published the aggregate's domain events; it now throwsConcurrentUpdateExceptionand publishes nothingupdateAllchecks each row's version instead of a batch update that cannot report per-row conflicts
v0.2.0
AI collaboration: guardrails for coding agents in generated projects, actionable architecture reports, and use cases as MCP tools.
Added
- Generated projects ship
AGENTS.mdandCLAUDE.mddescribing layers, conventions and themvn verifygate for AI coding agents - Claude Code Skills in generated projects:
ddk-add-aggregate,ddk-add-use-caseandddk-add-domain-eventfor the four-layer template,ddk-add-featurefor the three-layer template - Generated projects include
spring-boot-starter-webmvc-testfor MockMvc tests AGENTS.mdandCLAUDE.mdfor contributors to DDK itselfddk-mcp-starter: exposes application use cases as MCP tools on top of Spring AI 2.0, with streamable HTTP by default, Bean Validation on tool arguments, error-coded tool errors, hidden internals for unexpected exceptions and an audit log line per callCommonArchRules.MCP_TOOLS_MUST_RESIDE_IN_ADAPTERkeeps@McpToolmethods in the adapter layer- The user example exposes
register_user,get_useranddisable_useras MCP tools ArchGuard.checkevaluates all architecture rules at once and writestarget/archguard/violations.jsonandviolations.md, each violation with its rule, class and a concrete fix; generated projects and the example use it
Changed
- Generated projects and the example check architecture in one
ArchitectureTest.architectureIsRespected()throughArchGuard.check, instead of one test per rule - The planned
ddk-ai-starteris dropped: chat client configuration, structured output and token usage metrics are built into Spring AI 2.0
Fixed
scripts/ddk.shinstallsX.Y.Zinstead of the snapshot version whenDDK_REFnames a release tag such asv0.1.0
v0.1.0
The first release: the DDD foundation on a Spring Boot 4.1 baseline.
Added
- Domain model primitives in
com.ddk.core.domain:Identifier,ValueObject,Entity,AggregateRoot,DomainEvent,DomainEventPublisher,Specification, with a framework-free domain package enforced by tests GenericRepositorybacked by MyBatis-Plus, with Entity ↔ PO mappers that fail at startup when missing or duplicated, and optimistic locking throughAggregateRoot.version()- Starters under a unified
ddk.*namespace: web, MyBatis, domain events, Redis, two-level cache, named data sources, tracing, Seata and ArchGuard ddk-cache-starter: Caffeine (L1) + Redis (L2) with cross-instance invalidation, Redis failure fallback, TTL jitter and Micrometer metricsddk-redis-starter: a JSONRedisTemplatewhose deserialization only accepts allow-listed packagesddk-archguard-starter: three- and four-layer ArchUnit rules, domain purity rules, andDDK_INTERNALS_MUST_NOT_BE_USED- Three- and four-layer Maven archetypes, and the runnable
ddk-example-userapplication scripts/ddk.shto install DDK locally and generate projects in one command- Null-safety: every package in
ddk-core,ddk-mybatisand the starters is@NullMarkedwith JSpecify, and NullAway checks it at compile time - Javadoc and sources jars for every module, built by the
releaseprofile in CI and attached to GitHub Releases;internalpackages are left out of the Javadoc - Quality gates in
mvn verify: Spotless checks and per-module JaCoCo minimums (70% lines, 50% branches); CI on Java 21 and 25
Changed
- Baseline is Spring Boot 4.1, Spring Framework 7 and Jackson 3
ddk-web-startercontributes Jackson defaults throughJsonMapperBuilderCustomizerand serializesLongas a stringddk-tracer-starterbuilds onspring-boot-starter-opentelemetryinstead of the whole actuatorEntity.id(),AggregateRoot.version()andApiResponsedata are declared@Nullable;AbstractException.getArgs()returns an empty array instead ofnull- The Redis level of the two-level cache writes synchronously, because Spring Data Redis 4 writes asynchronously by default and that let stale values flow back into L1
Removed
RedisUtil; injectRedisTemplate<String, Object>directly
Internal
Starter implementation classes live in com.ddk.<starter>.starter.internal packages and are not public API: CacheInvalidationListener, CacheInvalidationMessage, RedisCacheInvalidationPublisher, MicrometerCacheMetrics, JitteredTtlFunction, SpringDomainEventPublisher and TraceIdResponseFilter. Generated projects check this with DDK_INTERNALS_MUST_NOT_BE_USED.
- The springdoc dependency from
ddk-web-starter; add it in the application when API docs are needed