Search code examples
wcfdomain-driven-designdatacontract

Populating association properties in entities from service call


Say I have a common pattern with a Customer object and a SalesOrder object. I have corresponding SalesOrderContract and CustomerContract objects that are similar, flatter objects used to serialize through a web service

public class Customer 
{
     public int CustomerId { get; set; }
     public string Name { get; set; }
     public Address ShippingAddress { get; set; }
     //more fields...
}

public class Order
{
     public int OrderId { get; set; }
     public Customer Customer { get; set;
     // etc
}

And my sales order contract looks like this

public class OrderContract
{
     public int OrderId { get; set; }
     public int CustomerId { get; set; }
}

public class OrderTranslator
{
     public static Order ToOrder(OrderContract contract)
     {
          return new Order { OrderId = contract.OrderId  };
          // just translate customer id or populate entire Customer object
     }
 }

I have a layer inbetween the service layer and business object layer that translates between the two. My question is this...do I populate the Order.Customer object on the other end since the Order table just needs the customer id. I don't carry the entire customer object in the OrderContract because it's not necessary and too heavy. But, as part of saving it, I have to validate that it's indeed a valid customer. I can do a few things

  1. Populate the Order.Customer object completely based on the CustomerId when I translate between contract and entity.. This would require calling the CustomerRepository in a helper class that translates between entities and contracts. Doesn't feel right to me. Translator should really just be data mapping.
  2. Create a domain service for each group of operations that performs the validation needed without populating the Order.Customer. This service would pull the Customer object based on Order.CustomerId and check to see if it's valid. Not sure on this because a sales order should be able to validate itself, but it's also not explicitly dealing with Orders as it also deals with Customers so maybe a domain service?
  3. Create a seperate property Order.CustomerId and lazy load the customer object based on this.
  4. Populate Order.Customer in from a factory class. Right now my factory classes are just for loading from database. I'm not really loading from datacontracts, but maybe it makes sense?

So the question is two part...if you have association properties in your enties that will be required to tell if something is completely valid before saving, do you just populate them? If you do, where you do actually do that because the contract/entity translator feels wrong?

The bottom line is that I need to be able to do something like

 if (order.Customer == null || !order.Customer.IsActive)
 {
      //do something
 }

The question is where does it make sense to do this? In reality my Order object has a lot of child entities required for validation and I don't want things to become bloated. This is why I'm considering making domain services to encapsulate validation since it's such a huge operation in my particular case (several hundred weird rules). But I also don't want to remove all logic making my objects just properties. Finding the balance is tough.

Hope that makes sense. If more background is required, let me know.


Solution

  • You have a couple of things going on here. I think part of the issue is mainly how you appear to have arranged your Translator class. Remember, for an entity, the whole concept is based on instance identity. So a Translator for an entity should not return a new object, it should return the correct instance of the object. That typically means you have to supply it with that instance in the first place.

    It is perhaps useful to think in terms of updates vs creating a new object.

    For an update the way I would structure this operation is as follows: I would have the web service that the application calls to get and return the contract objects. This web service calls both repositories and Translators to do it's work. The validation stays on the domain object.

    In code an update would look something like the following.

    Web Service:

    [WebService]
    public class OrderService
    {
        [WebMethod]
        public void UpdateOrder(OrderContract orderContract)
        {
            OrderRepository orderRepository = new OrderRepository(_session);
    
            // The key point here is we get the actual order itself
            // and so Customer and all other objects are already either populated
            // or available for lazy loading.
    
            Order order = orderRepository.GetOrderByOrderContract(orderContract);
    
            // The translator uses the OrderContract to update attribute fields on
            // the actual Order instance we need.
    
            OrderTranslator.OrderContractToOrder(ref order, orderContract);
    
            // We now have the specific order instance with any properties updated
            // so we can validate and then persist.
    
            if (order.Validate())
            {
                orderRepository.Update(order);
            }
            else
            {
                // Whatever
            }
        }
    }
    

    Translator:

    public static class OrderTranslator
    {
        public static void OrderContractToOrder(ref Order order, OrderContract orderContract)
        {
            // Here we update properties on the actual order instance passed in
            // instead of creating a new Order instance.
    
            order.SetSomeProperty(orderContract.SomeProperty);
            // ... etc.
        }
    
    }
    

    The key concept here is because we have an entity, we are getting the actual Order, the instance of the entity, and then using the translator to update attributes instead of creating a new Order instance. Because we are getting the original Order, not creating a new instance, presumably we can have all the associations either populated or populated by lazy load. We do not have to recreate any associations from an OrderContract so the issue goes away.

    I think the other part of the issue may be your understanding of how a factory is designed. It is true that for entities a Factory may not set all the possible attributes - the method could become hopelessly complex if it did.

    But what a factory is supposed to do is create all the associations for a new object so that the new object returned is in a valid state in terms of being a full and valid aggregate. Then the caller can set all the other various and sundry "simple" attributes.

    Anytime you have a Factory you have to make decisions about what parameters to pass in. Maybe in this case the web service gets the actual Customer and passes it to the factory as a parameter. Or Maybe the web service passes in an Id and the factory is responsible for getting the actual Customer instance. It will vary by specific situation but in any case, however it gets the other objects required, a factory should return at minimum a fully populated object in terms of it's graph, i.e all relationships should be present and traversible.

    In code a possible example of new Order creation might be:

    [WebService]
    public class OrderService
    {
    
        [WebMethod]
        public void SaveNewOrder(OrderContract orderContract)
        {
            // Lets assume in this case our Factory has a list of all Customers
            // so given an Id it can create the association.
    
            Order order = OrderFactory.CreateNewOrder(orderContract.CustomerId);
    
            // Once again we get the actual order itself, albeit it is new,
            // and so Customer and all other objects are already either populated
            // by the factory create method and/or are available for lazy loading.
    
            // We can now use the same translator to update all simple attribute fields on
            // the new Order instance.
    
            OrderTranslator.OrderContractToOrder(ref order, orderContract);
    
            // We now have the new order instance with all properties populated
            // so we can validate and then persist.
    
            if (order.Validate())
            {
                //Maybe you use a Repository - I use a unit of work but the concept is the same.
                orderRepository.Save(order);
            }
            else
            {
                //Whatever
            }
        }
    }
    

    So, hope that helps?