hey Kotlin server folks! i'm working with Ktor + E...
# server
r
hey Kotlin server folks! i'm working with Ktor + Exposed to manipulate a database, and i've come across a dilemma related to architecture. with Exposed, it seems that the way Entities are designed is that they are not really meant to be transformed to domain models unless you transform all the relationships at once. for example, we have a few properties that are referenced via another table using
referencedOn
. These are properties that are fetched lazily.
Copy code
object ClassEquipments : IntIdTable("class_equipments") {
  val equipmentId = integer("equipment_id").entityId().references(Equipments.id)
  val classId = integer("class_id").entityId().references(Classes.id)
  val createdAt = timestamp("created_at").default(Instant.now())
  val updatedAt = timestamp("updated_at").default(Instant.now())

  init {
    index("ce_class_id_equipment_id", true, classId, equipmentId)
  }
}

class ClassEquipmentEntity(id: EntityID<Int>) : IntEntity(id) {
  companion object : IntEntityClass<ClassEquipmentEntity>(ClassEquipments)

  var equipmentId by ClassEquipments.equipmentId
  var classId by ClassEquipments.classId
  val createdAt by ClassRatings.createdAt
  var updatedAt by ClassRatings.updatedAt
  val equipment by EquipmentEntity referencedOn Equipments.id
  val clazz by ClassEntity referencedOn Classes.id
}
Our transformation extension looks like this
Copy code
fun ClassEquipmentEntity.toModel(): ClassEquipment = ClassEquipment(
    id = id.value,
    equipmentId = equipmentId.value,
    classId = classId.value,
    clazz = clazz.toModel(),
    equipment = equipment.toModel(),
    updatedAt = updatedAt.offsetDateTime,
    createdAt = createdAt.offsetDateTime
)
We convert everything at once. If we always wanted a
clazz
and an
equipment
this would be fine. There are times, however, that we don't want these objects. Now, this could be a question of premature optimization, but I'm wondering if anyone has been able to have a nice data class representation of a domain model that also allows the flexibility to load relationships on demand? Or maybe this approach is flawed somehow? Really curious to hear from anyone familiar with this. Thanks in advance!
m
As you already said references are evaluated lazyly so you can add a parameter to your toModel() extension and then set e.g. clazz to null depending on the property
r
Essentially make optional model parameters, yeah. It seems like that will likely be the best approach for now. Thanks for the help
j
Architecturally, you should separate your interactions with a database in a separate layer. Inside that layer you are dealing with database fields, lazy loading, etc. None of that stuff should leak outside that layer. So a data class that triggers database activity as a side effect elsewhere in your architecture is a bad idea. It's exactly why things like hibernate have a reputation for bad performance because you always end up with model classes that have these kind of side effects. So isolate the database stuff in a repository or DAO class. Put business logic, including model construction in some service class. Probably you can do some clever things with reflection to look up constructor args by name. As there is no direct relation between the model and your DB layer, all model classes should be immutable too.
6
🙏 3
m
All that Jilles said applies, moreover one sentence in your message highlights a deeper issue:
We convert everything at once. If we always wanted a 
clazz
 and an 
equipment
 this would be fine. There are times, however, that we don’t want these objects.
This is exactly what Domain Driven Design solves with “bounded contexts” and “aggregates”, if you don’t know these concepts I suggest to read about them, it will be enlightening 😉 The problem you have is a classic one of stuffing too much data inside a single class that can’t map correctly with Domain Use Cases. Referring to DDD concepts above and CQRS, you need to have different Read Models/Bounded Contexts depending on your use cases, so when you need a set of properties you’ll have those only. You will need more classes, of course, but the design will be less complex and more isolated.
🙏 1
r
Really great advice, thank you. I was honestly leaning towards this approach of having multiple models representing the different use cases, but as you said, kinda sucks having to create all these models
👍 1
We're currently considering a builder pattern where we can build these models to be immutable with only the properties we need; however, i do feel this breaks the separation of concerns between layers
👍 1
m
You’re welcome, I always like to discuss software design 😉 Don’t be afraid of having multiple models: that’s how DDD handles complexity! (with “model” I mean one or more classes collaborating) The important part is that each model being coherent in one or more bounded context. It’s not a problem how you build the objects, that’s solved with factories, and it won’t break your layers (that depends on how you structure your code), because you’ll have DTOs or similar in the persistence layer, then those will be converted to a specific read/write model depending on the use case.
🙏 2
r
Yes makes sense
d
@Matteo Mirk hey I’m working with @Ryan Simon in this problem. DDD is very interesting, so are bounded contexts essentially like cookie cutters of the lowest level of data abstraction? And these things can’t change as they pass through layers? This seems like a lot of work as the layers add up specially when you get into subsets of single parameter names as an extreme example
m
Hi Drew, in a broad sense you could put it that way but I’m afraid it’s more complicated than that. You’ll have to read some articles (or better a book) on the subject, because DDD is a wide topic. But the essential part of it is that in your “domain layer” (let’s call it so) you have all the various models representing your business vocabulary (DDD’s “_ubiquitous language”_) and use cases. Your models don’t pass through other layers. That is the important core part, then around it you have progressively larger concepts like aggregates, bounded context, context map, etc. and a bunch of patterns used to implement them. The important thing to understand is that DDD focuses around the ubiquitous language and use cases, nothing else matters; the inputs and outputs (BD, for example) are just boundary details, because you don’t model your application around a DB schema (that’s the classic data-centric approach, so common in usual 3-tier web apps from the early 2000s), but you model it around the business language. Then, when persistence is needed, a Repository with DTOs (optional) can map your model to tables, but your domain layer is unaware of that and should never bend because you have some tables are built in such and such way, the model doesn’t know anything about the persistence or other layers.
👍 2
I’m afraid I can’t explain DDD properly on a chat, it would take a lot of words, sorry. But if you want you can read this small book which explains the fundamentals: http://www.carfield.com.hk/document/software+design/dddquickly.pdf
r
this is great Matteo, thank you so much
m
you’re welcome, I hope you can apply DDD successfully and resolve your problem!
👍 2
d
Thanks so much for the much needed info @Matteo Mirk 🙏🏽
👍 1