top of page

DDD-like Kotlin object cooperating with Spring context

  • Cyril Sahula
  • 3 hours ago
  • 2 min read


Diagram with "Domain Object" box linked to "Spring Context" cloud via "repository::function()" label. Arrows indicate interaction.

My friend Dominik helped me figure out which direction to take my coding skills. I never really liked the standard industry patterns that come with the Spring framework, where code is split into APIs, services, and repositories communicating via DTOs, DAOs, etc. I missed encapsulation and clear business objects. Plus, I enjoy both functional and OOP programming, and this layered approach often goes against those principles.

DDD is full of abstract rules, but I decided to follow two simple ones:


  1. Domain objects are created on the edge of the domain layer or from another domain object.

  2. A domain object is a real object — it contains both data and business logic.


Kotlin implementation

Sticking to this leads to fluent, readable code that's easy to test and keeps domain problems well-encapsulated. Unfortunately, the Spring framework doesn’t really support this approach by default — it manages beans and stores them in the application context. So the question became: how can I keep clean, OOP-style domain objects while still calling functions that rely on the application context, like opening a DB transaction?

The example below shows that if a function from a bean (usually a repository) is passed into a domain object as a lambda, the Spring context remains available — and even Spring aspects continue to work as expected.


// GraphQl endpoint and edge of a domain layer
@DgsComponent
class DeleteCertificateMutation(
    private val certificateDbAdapter: CertificateDbAdapter,
    private val validator: DomainValidator,
) {
    @DgsMutation
    suspend fun deleteCertificate(id: String): Boolean =
        validator.existsCertificate(id)
            .deleteCertificate(
                certificateDbAdapter::deleteCertificate,
            ).getOrThrow()
}

// Snipped from Certificate domain object 
open suspend fun deleteCertificate(
        deleteCertificate: suspend (CertificateId) -> Unit,
        getSupplier: suspend (CertificateId) -> SupplierId,
    ): Either<NotAdminError, Boolean> {
  		// some business logic before like checking right etc.
        deleteCertificate(this@CertificateId).run 
			{ supplier.recalculateStatus() }
        true
    }

Of course, there are also ways to avoid relying on the Spring context between the domain and repository layers, but in my case, I needed to use Spring Data R2DBC — probably the only reactive SQL database library that supports transactions — and it relies on aspects.

Conclusion

  • This approach allows you to have clean, OOP-style domain objects without losing the benefits of the Spring Framework environment.

  • Domain objects are easy to unit test without needing mocking frameworks — which often lead to brittle tests focused too much on implementation details.

  • To save time, I still use mock() for method or constructor arguments that aren't relevant to the test itself.

bottom of page