A Scala port of the Citerus DDD Sample — Eric Evans' canonical cargo-shipping example used to teach Domain-Driven Design tactical patterns: entities, value objects, aggregates, repositories, domain services, domain events, and specifications.
Migrated from Scala 2.8 to Scala 3.3.4 LTS on task007/scala3-15d0bf.
The build is on sbt 1.10 / Java 17. sbt compile, sbt Test/compile,
and sbt test are all green.
| Component | Version | Notes |
|---|---|---|
| Scala | 3.3.4 LTS | dialect: -source:3.3 |
| Build | sbt 1.10 | Maven was removed mid-migration |
| Java | 17 | required by Spring 5.2 / Hibernate 5.4 deps |
| Test framework | ScalaTest 3.2.19 | + scalatestplus-mockito |
| Mocking | mockito-scala 1.17 | EasyMock removed |
| Spring | 5.2.19 | javax.* era — Spring 6 is a follow-up |
| Hibernate | 5.4.24 | XML mappings preserved |
| CXF (JAX-WS) | 3.3.8 | for the booking facade |
See .claude/plans/scala3-upgrade.md for
the full plan and Migration notes below for what
was actually done.
src/main/scala/se/citerus/dddsample/
domain/{model,service,shared}/ ← framework-free DDD core
application/ ← use-case orchestration
infrastructure/{persistence,routing}/ ← Hibernate, external services
interfaces/{booking,handling,tracking}/ ← Spring MVC + CXF facades
The DDD invariant: domain.* never imports from infrastructure.* or
interfaces.*.
sbt compile # compile main sources
sbt test # run all tests against in-memory HSQLDB
sbt "testOnly *CargoTest" # run a single suite
sbt scalafmtAll # format the codebase
sbt scalafmtCheckAll # CI-style format check
# Web app (xsbt-web-plugin)
sbt "Jetty/start" # serve UI on :8080
sbt "Jetty/stop"
sbt package # build the WAR
sbt bookingFacadeJar # secondary jar for the JAX-WS facadeRequires JDK 17 and sbt 1.10+ on the PATH. Install sbt with
brew install sbt or via Coursier.
- Dependabot (
.github/dependabot.yml) — weekly sbt + GitHub Actions PRs, grouped by Spring / CXF / Hibernate / ActiveMQ / Jackson / logging / test frameworks. Defersorg.scala-lang*to Scala Steward. - Scala Steward (
.scala-steward.conf) — enroll by addingoluies/ddd-sample-scalato yourscala-steward-reposlist; Steward picks up the repo-local config automatically.
Project-local skills live under .claude/skills/:
scala3-migration— invoke when editing Scala sources during the Scala 2 → 3 migration.scala-ddd-tactical— invoke when adding to or refactoring the domain model.scala-testing— invoke when writing or porting tests.
CLAUDE.md at the repo root has the short briefing for the assistant.
- Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software, Addison-Wesley, 2003.
- Citerus DDD Sample (Java).
- Scala 3 migration guide.
The Scala 2.8 → 3.3 migration was done in 13 batches by package. Highlights of what changed beyond mechanical syntax:
Build
pom.xmldeleted;build.sbtis the source of truth.- xsbt-web-plugin 4.2.4 replaces the Maven WAR + Jetty plugin.
- Legacy
lib/scalatest-1.2-for-scala-2.8.0.RC3-SNAPSHOT.jarremoved (was an unmanaged dep that conflicted with ScalaTest 3.2.19). - Spring's old monolithic
org.springframework:springartifact was split intospring-core,spring-context,spring-beans,spring-aop,spring-tx,spring-orm,spring-jdbc,spring-jms,spring-web,spring-webmvc. - Strict scalac options (
-Wunused:imports,-Wvalue-discard,-Xfatal-warnings) are temporarily disabled inbuild.sbt. A cleanup PR should re-enable them and remove the now-unused imports.
Domain model — real Scala 3 changes (not just syntax)
CarrierMovementandLeghad constructor params that shadowed same-named methods (allowed in Scala 2, error in Scala 3). Params renamed to_departureTime/_arrivalTime/_loadTime/_unloadTimeand madeprivate val.Itinerary.END_OF_DAYSused the non-existentMath.MAX_LONG(latent bug masked by the old build) → fixed toLong.MaxValue.HandlingHistoryusedList.sort(predicate)(gone in Scala 2.13+) →sortWith.Deliveryreduced its non-localreturncount; a few remain insidefor/whileloops and emit deprecation warnings (Scala 3 wantsboundary/boundary.break). Behavior is unchanged.
Library renames
org.apache.commons.lang.*→org.apache.commons.lang3.*(38 sites).scala.reflect.BeanProperty→scala.beans.BeanProperty.scala.collection.JavaConversions→scala.jdk.CollectionConverters.org.hibernate.classic.Session→org.hibernate.Session.
Stubbed-out (need follow-up rewrite)
interfaces.tracking.CargoTrackingControllerextended Spring 2.x's removedSimpleFormController— class kept on the classpath as a stub with a TODO; request-handling logic needs to be ported to Spring 5@Controller/@GetMapping.infrastructure.persistence.hibernate.AbstractRepositoryTestandCargoRepositoryTestextended Spring 2.x's removedAbstractTransactionalDataSourceSpringContextTests. Stubbed with the original assertions preserved as TODO comments. Needs port to Spring 5 TestContext (SpringExtension/@SpringJUnitConfig).
Test rewrite
- 3 unit tests (
BookingServiceTest,HandlingEventServiceTest,RouteSpecificationTest) ported from JUnit 3 + EasyMock + ScalaTest 1.2 to ScalaTest 3.2AnyFunSuite+ Mockito. All 6 tests pass.
What's NOT done (deliberately, in scope for follow-up PRs)
- Spring 5 → 6 (Jakarta EE).
- Hibernate 5 → 6.
- Java 21 baseline.
- Filling in
CargoTrackingControllerand the integration tests. - Re-enabling strict scalac flags + cleaning up unused imports.
See license.txt — original Citerus license preserved.