#OOP extend entity with required and optional field - typesafe

1 messages · Page 1 of 1 (latest)

rain shard
#

Hey, I am building a REST API in a OOP way

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

tall streamBOT
#

🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)

crude cosmos
#

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.

rain shard
#

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)?

crude cosmos
#

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.

rain shard
#

Yea, I thought zod would be the one, who controls the output. Like I can return from my handler:

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

crude cosmos
#

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.

rain shard
#

hmm ok. Then I'll do it like you said. Thanks!

tall streamBOT
crude cosmos
#

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.

rain shard
crude cosmos
#

That's why people prefer to consolidate them by feature and not drop them all in a single folder.