C
C#•4mo ago
Dyad

OOP and REST/JSON Design

public class Person { public string FirstName {get;set;} public string MiddleName {get;set;} public string LastName {get;set;} public string FullName {get {return $"{FirstName} {MiddleName} {FullName}";}} } When creating a GET endpoint for a resource, should I... 1) Pre-compute method results such as FullName() and send to the client? 2) Re-implement methods such as FullName() client side? Thoughts: - Pre-computation would prevent me from having to duplicate the logic client side, and any time the FullName() logic changes, it would only have to be updated in one spot, the server - FullName() is a pretty trivial example, but I can imagine more complex functions with ever changing rules / validation - If we pre-compute and serialize function results, it seems like we might run into different problems - For example, the client does not know about the function dependencies and is left guessing when to re-fetch a person to update FullName() - A PUT/PATCH request could re-send the representation after modification - However, this is a trivial example, and doesn't work so well when there are parent/child dependencies, etc - For example, let's imagine a Shopping Cart and its children Line Items. Cart.TotalPrice() might be a function that sums up the line item prices and their quantities. If the client changes a single line item's quantity, the client would have to be smart enough to re-fetch the entire shopping cart to get the new total. Or..., the PUT/PATCH request could respond with the entire cart and not just the modified line item - It seems like pre-computing and serializing function results hides object relationships/dependencies from the client, leaving the client "guessing" when state becomes stale - It seems like re-implementing methods client side is not very DRY and could cause sync issues with ever changing business rules Would love to hear some opinions / advice on this! 🙂
8 Replies
Angius
Angius•4mo ago
Ideally, the data you get from the database should be in the exact shape you need So doing the full name and similar stuff would be a part of your .Select() mapping Or any other form of mapping you'd use Also, your
public string FullName {get {return $"{FirstName} {MiddleName} {FullName}";}}
public string FullName {get {return $"{FirstName} {MiddleName} {FullName}";}}
can just be
public string FullName => $"{FirstName} {MiddleName} {FullName}";
public string FullName => $"{FirstName} {MiddleName} {FullName}";
Just a side note
Dyad
Dyad•4mo ago
FullName would not be in the database, but a function on the business model and I'm wondering if the function result should be in the DTO that makes its way to the client
Angius
Angius•4mo ago
For simple stuff like this, I'd say just don't have it and let the client worry about assembling all 3 names into a full name Your example with the sum of all cart items, though, I would calculate at the moment of fetching it from the db And not have it be a function of any model or DTO
Dyad
Dyad•4mo ago
Where would that logic be located if not on a class?
Angius
Angius•4mo ago
Wherever you get your data EF example:
var cart = await _context.Carts
.Where(c => c.UserId == uid)
.Select(c => new CartDto {
TotalItems = c.Items.Count(),
TotalCost = c.Items.Sum(i => i.Price)
})
.FirstOrDefaultAsync();
var cart = await _context.Carts
.Where(c => c.UserId == uid)
.Select(c => new CartDto {
TotalItems = c.Items.Count(),
TotalCost = c.Items.Sum(i => i.Price)
})
.FirstOrDefaultAsync();
Dyad
Dyad•4mo ago
Ok so you would still pre-compute before sending to the client
Angius
Angius•4mo ago
Where it makes sense It doesn't make sense to precompute the full name. The client can do it easily It makes sense to precompute total items and total cost, on the database, instead of sending a whole-ass list of items If the client doesn't need that list of items
Dyad
Dyad•4mo ago
Oh, I was imagining the client would need all the items and the total it would be a cart screen but Imagining the TotalCost function could be pretty complex if there is shipping, taxes, etc which leaves the question, how does the client know when the DTO becomes stale and they need to re-fetch? Is it just good documentation? or that modification requests such as PUT/POST/PATCH respond with updated dependencies? So if you update an item, the response has all resources that were updated as a consequence