OOP extend entity with required and optional field - typesafe
Answered
B33fb0n3 posted this in #help-forum
B33fb0n3OP
Hey, I am building a REST API in a OOP way
I have a
Now when I fetch a list of products, that don't contain a "owner" relation, everything is fine and works. I create the objects and they get returned, serialized through zod and returned correctly.
But when I have a "get single product" route, the
Best case would be, that I call
I am using typescript. How to deal with relations like that?
Builder (without returning Object): https://pastebin.com/3tNyjhrU
I have a
Product
entity. This entity contains currently only variables that are really needed for the Product. So stuff like: price, content, title, ... Relations like "owner" are not included to keep things clean.Now when I fetch a list of products, that don't contain a "owner" relation, everything is fine and works. I create the objects and they get returned, serialized through zod and returned correctly.
But when I have a "get single product" route, the
owner
should return with it. Like I need to build a product object, that contains an owner (not owner: User | undefined
like before, now it's: owner: User
). I can easily extend the object, yes. And that works, yes. But that might not be the only relation that I have to another object. Maybe there are pictures in the future, maybe variants, ...Best case would be, that I call
setOwner(owner: User)
and then the object itself knows that it contains an owner.I am using typescript. How to deal with relations like that?
Builder (without returning Object): https://pastebin.com/3tNyjhrU
Answered by Asian black bear
You typically have a
ProductDto
for the /products
endpoint and if your /products/:id
endpoint returns more details you have a ProductDetailsDto
unique to that endpoint. Technically you can inherit from ProductDto
which works in some cases, but can cause some issues if changes to the parent shouldn't be reflected in the details. As such it's also common to not inherit and just be explicit about the DTOs transferred as a response of those endpoints.11 Replies
Asian black bear
You typically have a
ProductDto
for the /products
endpoint and if your /products/:id
endpoint returns more details you have a ProductDetailsDto
unique to that endpoint. Technically you can inherit from ProductDto
which works in some cases, but can cause some issues if changes to the parent shouldn't be reflected in the details. As such it's also common to not inherit and just be explicit about the DTOs transferred as a response of those endpoints.Answer
B33fb0n3OP
So at the end instead of returning the product object (that is then serialized with zod), I create an explicit DTO object, that will consume in this case my product details (the basic ones) and an owner (user object)?
Asian black bear
Yes, you never expose your exact entity. You build the DTO from the entity manually or use a mapper for that. In case you need to enrich the returned object you combine all the relevant data into the DTO.
B33fb0n3OP
Yea, I thought zod would be the one, who controls the output. Like I can return from my handler:
And when zod is like this:
Then the response of the API would be:
Even if I returned the whole object. That also works for nested objects
return {
id: "id_13456",
title: "some title",
description: "some wonderful desriptipn"
}
And when zod is like this:
const result = z.object({
id: z.string(),
})
Then the response of the API would be:
{
id: "id_13456",
}
Even if I returned the whole object. That also works for nested objects
Asian black bear
My original point still stands: don't try to reuse needlessly complex objects with nullable fields across multiple endpoints that return different responses. Make the DTOs unique to each endpoint if they are not strictly the same, sometimes it even makes sense to duplicate them to make changes more comfortable.
How you map is up to you, it can be a trivial or sophisticated as you need it to be.
B33fb0n3OP
hmm ok. Then I'll do it like you said. Thanks!
Asian black bear
One thing that may be of interest for really advanced enterprise projects if you work on those at any point in time: rather than using CRUD some elect to work with CQRS. In this architecture you model commands and queries very granular and specific to their capabilities or features and end up with unique DTOs for each of them.
@Asian black bear One thing that may be of interest for really advanced enterprise projects if you work on those at any point in time: rather than using CRUD some elect to work with CQRS. In this architecture you model commands and queries very granular and specific to their capabilities or features and end up with unique DTOs for each of them.
B33fb0n3OP
yea, the only thing that feels just wrong for me is to create soooo much DTOs. It looks just... ugly... and thats only for 4 objects in total... my folder will blow up, when I have more tables. (Ignore the "Request". They are plain json objects)
Asian black bear
That's why people prefer to consolidate them by feature and not drop them all in a single folder.
B33fb0n3OP
hm yea, ok