C
C#6mo ago
UltraWelfare

Organizing and naming different closely related Dtos

My current setup is as follows (I name my Dtos as 'Models'):
Orders (this is the base order class)
- Models
-- OrderModel.cs
-- OrderModelMapper.cs
- OrderService.cs


OrderItems (every order has 1:N order items)
- Models
-- OrderItemModel
-- OrderItemModelMapper


DraftOrders (a draft order extends order)
- Models
-- DraftOrderModel.cs
-- DraftOrderModelMapper.cs
- DraftOrderService.cs


ReceiptOrders (a receipt order extends order)
- Models
-- ReceiptOrderModel.cs
-- ReceiptOrderModelMapper.cs
- ReceiptOrderService.cs
Orders (this is the base order class)
- Models
-- OrderModel.cs
-- OrderModelMapper.cs
- OrderService.cs


OrderItems (every order has 1:N order items)
- Models
-- OrderItemModel
-- OrderItemModelMapper


DraftOrders (a draft order extends order)
- Models
-- DraftOrderModel.cs
-- DraftOrderModelMapper.cs
- DraftOrderService.cs


ReceiptOrders (a receipt order extends order)
- Models
-- ReceiptOrderModel.cs
-- ReceiptOrderModelMapper.cs
- ReceiptOrderService.cs
The order can be of different types (such as draft or receipt, and there's more but I'm keeping it simple). But we also need a way to fetch multiple types at once (hence why a basic Order exists). There is reason that there's a difference between all of them (some relationship constraints when orders are being transformed). My main problem is the mess that is happening between the models. The DraftOrderModel should expose a List<ReceiptOrderModel>. However when you receive the draft order, you don't want the -full- details of the receipt order (since they contain all the order items etc which is not needed). You want a slimmer model of the receipt order that contains basic stuff such as its id, it's title and maybe its price... But I'm not sure how to make this..? Should I make a new DraftOrderReceiptOrderShortModel ? That sounds obnoxious.... Should I make a nested class ReceiptOrderShortModel inside DraftOrderModel ? Should I make a general OrderShortModel and reuse it whenever I want ? I'm not sure how to continue without making it more of a mess:)
22 Replies
Honza K.
Honza K.6mo ago
Use interfaces instead of base classes for this
UltraWelfare
UltraWelfare6mo ago
Hm, can you give me an example?
Honza K.
Honza K.6mo ago
or you could just have List<ReceiptOrderId> in the DraftOrderModel and if you need the specific order you go and fetch it? We currently have this and it works actually really good. We have a Transaction that can have subsequent transactions and instead of having the full Transaction object in a list we just have list of IDs... The Transaction model is like "oh, you want my children? sure, here you go"... "what? you want its details? I dunno, go fetch yourself, I don't really care bro"
UltraWelfare
UltraWelfare6mo ago
We typically need some stuff for display, so it would always end up in 2 calls That's why I want a short variant of it inside the model
Honza K.
Honza K.6mo ago
is it going to be a system with a lot of users and a lot of records in the DB? (like 500 000 users and over 1M records)
UltraWelfare
UltraWelfare6mo ago
Nope, it's a LAN system with no more than 1-2 computers online
Honza K.
Honza K.6mo ago
so is it even worth it to you to care for the 100-200ms for second call?
UltraWelfare
UltraWelfare6mo ago
I'll probably do make a general OrderShortModel and reuse it Hm... I think it's easier to have everything at one go instead of back-n-forth calling
Honza K.
Honza K.6mo ago
if I would have to care about that I would probably use the interfaces
interface IOrder
{
public Guid ID { get; }
public decimal Price { get; set; }
}

interface IDraft
{
public string Name { get; set; }
public ICustomer Customer { get; set; }
}

interface IReceipt
{
public IReadOnlyList<IINamedOrder> Items { get; set; }
}

interface IINamedOrder : IOrder
{
public string Name { get; set; }
}

class DraftModel : IDraft, IINamedOrder, IOrder
{
public Guid ID { get; }
public decimal Price { get; set; }
public string Name { get; set; }
public ICustomer Customer { get; set; }
}

class Receipt : IReceipt
{
public IReadOnlyList<IINamedOrder> Items { get; set; }
}
interface IOrder
{
public Guid ID { get; }
public decimal Price { get; set; }
}

interface IDraft
{
public string Name { get; set; }
public ICustomer Customer { get; set; }
}

interface IReceipt
{
public IReadOnlyList<IINamedOrder> Items { get; set; }
}

interface IINamedOrder : IOrder
{
public string Name { get; set; }
}

class DraftModel : IDraft, IINamedOrder, IOrder
{
public Guid ID { get; }
public decimal Price { get; set; }
public string Name { get; set; }
public ICustomer Customer { get; set; }
}

class Receipt : IReceipt
{
public IReadOnlyList<IINamedOrder> Items { get; set; }
}
IMO the solution with the model just knowing the appropriate IDs is used more in practice (I could be wrong tho)
UltraWelfare
UltraWelfare6mo ago
it depends on REST APIs I've seen in the world.. a) 90% of the cases you need it ? I'll embed it b) 90% of the cases you don't need it ? Here's the id if you need it It's the same concept as order items... Most of the times you need them, so when you fetch an order they come by default you don't go to a separate endpoint
Honza K.
Honza K.6mo ago
yeah, but you can implement multiple interfaces you cannot inherit multiple base classes
UltraWelfare
UltraWelfare6mo ago
Yeah I'll probably do an IOrder interface however my only painpoint is having a List<IOrder> GetOrders which should return a list of all of them, no matter the type but it's not a big deal
Honza K.
Honza K.6mo ago
so you want a specific order? or list of orders with only some items?
UltraWelfare
UltraWelfare6mo ago
Both will be needed There's a place where all orders can be shown (with filters etc) and theres a place where you only receive draft orders and a separate place for only receipt orders I basically have every usecase, that's why it makes it annoying
Honza K.
Honza K.6mo ago
are you building an API and a UI? Both are internal for your company/personal only?
UltraWelfare
UltraWelfare6mo ago
It's a desktop app that will likely need to expose an API at some point in the near future The API will be ""public"" for other apps inside the same PC to be used We have a library that contains all this stuff that is shared between API and Desktop Desktop just does the native call, API exposes the call through a controller
Honza K.
Honza K.6mo ago
i see
UltraWelfare
UltraWelfare6mo ago
So all the "services" are the core part of the app... The UI is just glue code to visuals and the controllers are also glue code... It's as separated as it should be or "abstracted" in better terms
Honza K.
Honza K.6mo ago
maybe you could build the API exactly for the needs of your UI?
UltraWelfare
UltraWelfare6mo ago
Eh no approach is wrong. Although I wanted to be closer to APIs since it's a good way of thinking And minimizing RAM usage (since you don't load everything at once)
Honza K.
Honza K.6mo ago
at work we have an API for a service, the service is public but we're the only one using the API and we're distributing the client apps... and let me tell you, the API is done exactly as REST API should be... but oh boy, it has so many endpoints and it's so fucking complex to do the simplest things... I wish the API was tailored to the applications... To get an account that owns a device, I have to get account id from the device detail, to get the device detail i have to fetch it via device id and then fetch the owning account... Why? because that's how the rest api should be... We have 3 applications, no client ever can call the API itself (we won'T give them the crypto protocol documentation so they couldn't authorize :D) but no, there cannot be an endpoint that would give the app exactly what the app needs, that would go against the api design what I want to say is, consider building the API for the needs of the UI, it can save you a lot of work and it could actually also save you some troubleshooting
UltraWelfare
UltraWelfare6mo ago
Oh yeah that sounds like a bit of a microservice hell Maybe you could have the expand pattern like stripe implements basically dynamic include /orders?expand=items and you can ask which relationships to load I'm not sure how this can be implemented in C# but I bet it can be done