Hi guys, topic of transforming domain objects into...
# getting-started
k
Hi guys, topic of transforming domain objects into Dto (or enything else), I know I can do something like:
Copy code
class Something(val name: String) {
    companion object {
        fun toDto(): Dto(name)
    }
}

data class Dto(val name: String)
But I would prefer to have possibility to write:
Copy code
Dto.from(foo: Something) = Dto(name = foo.name)
Is it possible?
k
you mean you don't want companion object in Dto?
k
I would prefer to avoid companion object on Dto, if possible
k
fun Something.toDto() = Dto(this.name)
?
k
But this looks like I am adding some functionality to the domain object, which at first glance looks like breaking the principles
especially if there are multiple external systems and each maps
Something
into different Dto
k
well I would go with companion object. maybe take a look at https://kt.academy/article/ek-factory-functions
k
Companion object on the
Dto
or
Something
k
Dto
domain should not be bothered with "infra"
j
You can also declare extensions on the companion but outside the class itself:
Copy code
fun Dto.Companion.from(..)
k
The problem may be when having multimodule app, whith mediator module that would bind together Domain (containing domain classes) and API (containing dtoS). API will not know about domain, thus would not be possible to add mappiing from domain to dto inside of companion object of DTO
j
The point was to extend the companion object of the Dto class but not declare that function in the class (so you can declare this outside the module)
k
aha!
k
in mediator you also have to have some contract right?
k
right
k
and it either can be domain or some 3rd object, right?
anyway, I would avoid having domain object to know about outer layer objects
so there have to be some shell around it doing conversions
k
Correct, yes
@Joffrey I like the idea of extending companion object, but does it mean I have to specify
companion object {}
on the DTO? Or is there some magic to be able to do
Copy code
data class Dto(val name: String)

fun Dto.Companion.from(smth: Something) = Dto(name = smth.name)

// Or do I have to write 
data class Dto(val name: String) {
    companion object { }
}
j
I remember having to write an empty companion at some point, but I thought they intended to remove this constraint. Maybe it's still necessary 😕 I also don't like those empty companions to be frank. If it's still necessary, I'd rather go for the regular extension the other way around without companion:
fun Domain.toDto(): Dto
k
This actually seems to be a convention in Kotlin, when checking Boolean implementation (there is an empty companion object too)
So, I landed on a file with top-level functions to map from domain to dto, where these functions are extension of type
Copy code
fun Dto.Companion.from(obj: Domain) = Dto()
j
This begs the question though, why do you dislike the
toDto()
version so much?
k
The reason is the domain object in this particular case is being translated into multiple different DTOs. In consequence, having:
Copy code
dto: SomeDto = domain.toDto()
dto: SomeOtherDto = domain.toDto()
dto: YetAnotherDto = domina.toDto()
which reads worse than
Copy code
dto = SomeDto.from(domain)
dto = SomeOtherDto.from(domain)
dto = YetAnotherDto.from(domain)
j
That would look really nice to me:
Copy code
val dto = domain.toSomeDto()
val dto = domain.toSomeOtherDto()
val dto = domain.toYetAnotherDto()
It goes in line with Kotlin's
String.toInt()
and the likes. And it also reads nicer (IMO) when chained, especially with nullable values:
Copy code
val dto = something.getSomeDomainEntitySomehow()?.toSomeDto()
As opposed to:
Copy code
val dto = someting.getSomeDomainEntitySomehow()?.let { SomeDto.from(it) }
k
Yes, those are good points, but some of those transformation may need additional information, which would made it
Copy code
domain.toSomeDto(other: Info)
vs
Copy code
SomeDto.from(domain, other)
which IMHO reads easier in the way of understanding how
someDto
composed
j
If the resulting DTO is an aggregation of data from the domain and from the extra params, and the other info is as important as the one from the domain, then I can see your point. The factory function might be better in this case. However, if the extra info is just a side thing, or information about how to transform from domain to DTO, I would still go for the
toDto(extra info)
variant, like
toInt(radix)
. It always boils down to the message you want to convey, I'll let you judge of that 🙂
k
That was very nice discussion and interesting points mentioned, thank you 🙂
👍 1