Catch-up
- I've been away from the Java world for a while - get back into that
New learning
- I've never used Spring - first steps with this
- I'm not familiar with DDD - let's understand some code patterns here
- I've never worked on financial applications before - read up & apply where relevant
- Builds use Gradle.
- Code has only been tested on Java 17. It uses
var
(Java 10) andrecord
(Java 14). - CI is via Github Actions: https://github.com/georgemarrows/SpringExpts/actions
./gradlew bootRun
- Server is at
localhost:8080
- There is a built-in test contact with
customerId=a fixed id for testing
- Endpoints are
./gradlew test
- DONE shell / walking skeleton for requests (hardcoded data) + integration tests
- DONE CI/CD using Github Actions
- DONE notes on patterns for financial applications (accounts / transactions)
- DONE flesh out domain model + unit/integration tests
- (maybe) simple UI
- (maybe) separate services
- (maybe) report coverage to Codecov
-
Learning (the beginnings of) Spring here was tricky. It's a big beast. Their getting started info is useful, but then there's a giant leap to their Javadoc, which is thin and not at all oriented around how to use it. Fortunately Baeldung does a good job of stepping in. StackOverflow is very helpful too.
-
I learnt even less of DDD, and definitely only at the "tactical" code level. ("Strategic"/"tactical" terminology from this article.) I'm beginning to get a sense of "why DDD?" at the level of code, and it's obvious that the clean domain models could be very useful. But issues like aggregates are still a mystery.
It's interesting to compare the mutable DDD domain model with the immutable model suggested in Boundaries by Gary Bernhardt. Immutable objects can be a lot easier to understand. Putting aside mutability, the overall structure is similar, both being careful to separate the communications technology (database, other services) from the internal domain model.
This is a useful corrective to getting DDD-obsessed. CRUD is the right solution for many subsystems ("bounded contexts").
-
Turning to the code, I'm not happy about how services & repositories are overly tangled, with eg
CustomerService
talking toAccountRepository
as well asCustomerRepository
. I doubtless need to understand the use of aggregates better. -
Good to see a couple of shortcuts in modern Java:
- Using
record
for easy de/serialising of JSON in controllers, or returning multiple results from a method - AssertJ's
extracting
makes some asserts much more readable
- Using
-
Dividing up code by packages
controller
/repository
/domain
is prioritising the language of the technical solution over the language of the problem (customer
,account
). This is ok in a small codebase like this one, but it's terrible in a larger monolith. I've worked with large codebases where the first dividing lines in the code were similarly technology driven, and it makes the problem domain hard to see in the code. I spent time with the team of one of these codebases moving the top-level splits towards being domain-first, and that was very helpful.On the other hand, the current package structure is helpful for slicing into the standard DDD layers.
-
Code is probably insufficiently commented. I'm not a fan of large swathes of comments doc'ing the simple or obvious, and none of the code here is at all algorithmically complicated or contains hidden gotchas or important cross-references. Those are the kind of things I would document.
-
I've baked in
InMemoryAccountRepository
, which should be switchable for a persistent, database-backed repository. I haven't spent time yet figuring out how to use Spring DI to do that. There's likely to be a few knock-ons on services and perhaps the domain model too.- As a side effect of not integrating with a database or ORM, I'm not yet sure how to make
CustomerService.createAccount()
transactional. Pretty important!
- As a side effect of not integrating with a database or ORM, I'm not yet sure how to make
-
I don't yet understand how DDD's aggregates would handle unbounded data like transactions for an account.
-
Money comes from nowhere! What account should be debited when the customer's new account is credited? This made me realise I don't know how banks track money flowing into their business. That's why
Transaction.dummyFromAccountId
exists. -
Money should be a DDD value object
-
Nothing on authentication or authorisation
- (Security) Spring leaks a lot of internal implementation information if you post data in the wrong format (eg no body) - how to turn this off?
- Looks like it's a dev env thing. Doesn't leak info when running under integration tests.
- Getting started - https://spring.io/guides/gs/spring-boot/
- Spring & Gradle - https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/
- Logging - https://www.baeldung.com/spring-boot-logging
- Dependency injection
- Event Storming and Spring with a Splash of DDD Interesting end-to-end analysis from event storming stickies through to Java code
- Organizing Layers Using Hexagonal Architecture, DDD, and Spring Much lighter, more code-focussed example
- Strategic Domain-Driven Design Book-length (and book quality writing from my initial impression). One to read later.
- Architectural Layers and Modeling Domain Logic Points out that you don't always need a domain model. Some bounded contexts genuinely are just CRUD and anything more than controller + repository is over-engineering.
- Domain Driven Design Quickly Not downloaded yet.
This is a thoughtful article on moving beyond simple CRUD for APIs: https://blog.palantir.com/rethinking-crud-for-rest-api-designs-a2a8287dc2af
Don't use the test pyramid? https://engineering.atspotify.com/2018/01/testing-of-microservices/
New things in Java land since I was last here...
var
declarations- Record syntax help cut down on noise for read-only / value classes
- AssertJ exists. Not sure what advantages are over hamcrest (apart from having a more obvious name!)
- java.time replaces JodaTime. There's also https://www.threeten.org/threeten-extra/
- """ multi-line strings have to start with a new line :-(