Skip to content

Linear design approach

ahanusa edited this page Oct 21, 2015 · 32 revisions

Peasy was designed to free ourselves from complications we often face in domain-driven designs, and favors a more linear approach to design and development.

What do we mean by linear approach? Basically we mean keeping 1-1 mappings between DTOs and data store entities (database tables, documents, etc.) because doing so greatly simplifies insert, update, and delete logic within the peasy framework. This can be attributed to the fact that during one of these operations, you don't have to inspect a DTO for the existence of children (the case for inserts and deletes) or the existence of dirty children (the case for updates).

Let's look at this in-depth. Suppose your database contains tables to represent customers, orders, and order items. Now let's suppose that we have DTOs to represent these tables and their relations to each other by exposing a Customer DTO that exposes a list of Order DTOs, and each Order DTO exposing a list of OrderItem DTOs.

Here's a code sample:

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderDate { get; set; }
    public List<OrderItem> OrderItems { get; set; }
}

public class OrderItem
{
    public int ID { get; set; }
    public int OrderID { get; set; }
    public decimal Amount { get; set; }
    public int StatusID { get; set; }
}

Now let's suppose you have created a customer service, exposing an UpdateCommand method that accepts an instance of a Customer DTO as an argument. To update it, you are now responsible for looping through each Order DTO in the Customer.Orders list, checking for new orders (which would call for an insert operation), checking to see that orders haven't been removed (which would call for a delete operation), or checking to see that data has changed (which would call for an update operation). This same logic then would have to be applied to OrderItem DTO instances stored in each Order DTO.

While you could just ignore children during an update operation against the customers service, you are still left with a customer DTO with dangling children properties that have little to no context within an update operation, which smells. You could also create different Customer DTOs that provide different properties and expose children of different nesting levels. However, you would then be left with many different Customer DTO implementations that leave a developer unsure when to use which one, etc.

An exception to all of this would be an implementation of CQRS, where nested DTOs would be acceptable as you would have specific handlers executing specific commands that accept specific DTOs as arguments. While the peasy framework could easily support a CQRS implementation, we are really arguing from a more traditional CRUD style type of application.

So how do we get around this design issue with peasy?

When you keep your DTO designs linear, your intentions about its usage are always clear. Therefore, it is recommended that DTOs map directly to a data store entity (database table, document, etc.). While a DTO can map across multiple data store entities providing children collections, it is encouraged to keep a 1-1 mapping between DTO and data store entity (database table, document, etc.).

Handling parent / child scenarios

When needing to support an insert, update, or delete scenario involving data from different data store entities, it is a peasy best practice to orchestrate this logic in a custom command implementation. For example, let's take the Customer, Order, and OrderItem example. Below are the classes without children references:

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderDate { get; set; }
}

public class OrderItem
{
    public int ID { get; set; }
    public int OrderID { get; set; }
    public decimal Amount { get; set; }
    public int StatusID { get; set; }
}
Clone this wiki locally