When using something like a good old controller -&...
# server
g
When using something like a good old controller -> service -> repository architecture. Do you have a model representation for each layer? For example:
Copy code
data class StudentDTO(val id: Long, val name: String)
data class Student(val id: Long, val name: String)
This would probably work easier in java (for good and bad reasons), given that the ID could be null, given we use
StudentDTO
as the
RequestBody
in a
POST
request. I don't like the approach with nullable id in the DTO. So I guess you would have to create multiple models for different scenarios?
Copy code
data class CreateStudentDTO(val name: String) --> Used when parsing the request
data class ReturnStudentDTO(val id: Long, name: String) --> Used to return from the controller
data class CreateStudent(val name: String) -> Used as an argument in the service layer
data class Student(val id: Long, name: String) -> Used as a return in the service layer
a
Yes, I often have several versions of the model; view/update variants for each layer of the architecture. It's tedious, but helps keep things safe and backwards compatible as you iterate on the repo/service/controller layers independently. 1. Database Model: Distinct from the business model, because it needs to be backwards and forwards compatible with older and newer versions of the schema. Also contains whatever mapper annotations you might need, to keep the business model clean. Sometimes these models can be omitted, based on your repository technology. 2. Business Models: with View/Update variants; the universal source of truth to perform all your business logic on 3. DTOs: with view/update variants; contains any supplementary computed properties, and formatted for however your consumers want it. There are often several versions for older and newer versions of your (API/worker/etc.) interface
4
g
I see. If you don't mind me asking. How do you group the different variants together? Is there any naming conventions one could follow here as well?
a
Sure, if we were to make a model for a cat, I might end up with something like: Database Models: I prefix with the database technology: e.g.
JpaCat
,
DynamoCat
, etc. Exception for exposed, which has
ExposedCatTable
, and potentially
ExposedCatEntity
. Business Models: plain, or suffixed with variant, eg.
Cat
,
CatUpdateData
, etc DTOs: Suffix the business model name with the DTO version: e.g.
CatDtoV1
,
CatDtoV2
,
CatUpdateDataV2
, etc.
j
We just default our values to some sort of dummy value (0 for the id, "" for the strings, etc) in the DTOs for non-nullable properties. To handle partial updates, we like to use readerForUpdating, so we don't have to differentiate between "Did the customer want to set this to null, or did they just not fill it in on a PATCH request"? https://www.logicbig.com/tutorials/misc/jackson/reader-for-updating.html
Allows us to generally have 1 DTO (rarely 1 view, one shared insert&update dto) and 1 model per table.
g
Awesome. Thanks guys! One last question. How do you group all the different abstractions of the cat? Do you have a
cat
package with all the different variations, or do you have a
DTO
package with all different DTOs (dogs, cats). As well as a
Exposed
package, and so on...
a
That's something that can vary widely from service to service; depending on the architecture. For a smaller service, I might do something like this: • petsapp ◦ Main • petsapp.api.v1 ◦ CatDtoV1 ◦ DogDtoV1 ◦ HttpApiV1 • petsapp.cat ◦ CatService ◦ Cat ◦ DynamoCatDao ◦ DynamoCat • petasapp.dog ◦ DogService ◦ Dog ◦ ExposedDogDao ◦ ExposedDogTable
g
I see. Thank you very much for all the input.
p
I agree - packaging by feature (in the petstore example, “cat” and “dog”) scales better than by layer (“data”, “api”, “controller”) especially when you get to bigger, more mature projects. You can always sub-divide the feature packages later if things get complicated.
g
Yeah, I see that now. Started of by packaging by layer. But each layer is now too big. So I kinda have to package it another way. I think I'll go by feature.
k
@Andrew O'Hara why api not by feature?
a
@kqr This hybrid approach is my personal preference for services with a smaller footprint. You have to tie all the features together at some point, so in my eyes, it might as well be at the api-level.