在服务调用的实体中填充关联属性

时间:2011-05-05 22:42:41

标签: wcf domain-driven-design datacontract

假设我有一个Customer对象和SalesOrder对象的公共模式。我有相应的SalesOrderContract和CustomerContract对象,它们是用于通过Web服务序列化的类似,更平的对象

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
}

我的销售订单合同看起来像这样

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
     }
 }

我在服务层和业务对象层之间有一个层,可以在两者之间进行转换。我的问题是这个...我在另一端填充Order.Customer对象,因为Order表只需要客户ID。我没有在OrderContract中携带整个客户对象,因为它没有必要且太重。但是,作为保存它的一部分,我必须验证它确实是一个有效的客户。我可以做一些事情

  1. 在合同和实体之间进行转换时,完全基于CustomerId填充Order.Customer对象。这需要在实体和合同之间进行转换的帮助程序类中调用CustomerRepository。对我来说感觉不对。译者应该只是数据映射。
  2. 为执行所需验证的每组操作创建域服务,而不填充Order.Customer。此服务将基于Order.CustomerId拉取Customer对象,并检查它是否有效。不确定,因为销售订单应该能够验证自己,但它也没有明确处理订单,因为它也处理客户,所以可能是域名服务?
  3. 创建一个单独的属性Order.CustomerId并基于此延迟加载客户对象。
  4. 从工厂类填充Order.Customer。现在我的工厂类只是用于从数据库加载。我不是真正从datacontracts加载,但也许它有意义吗?
  5. 所以问题是两部分......如果你的内容中有关联属性需要在保存之前判断某些内容是否完全有效,那么你只是填充它们吗?如果你这样做,你实际上在那里做的是因为合同/实体翻译错了?

    底线是我需要能够做类似

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

    问题是这样做有意义吗?实际上,我的Order对象有很多验证所需的子实体,我不希望事情变得臃肿。这就是为什么我正在考虑使用域服务来封装验证,因为在我的特定情况下它是如此庞大的操作(几百个奇怪的规则)。但我也不想删除所有逻辑,使我的对象只是属性。找到平衡很难。

    希望这是有道理的。如果需要更多背景知识,请与我们联系。

1 个答案:

答案 0 :(得分:0)

你有几件事情在这里发生。我认为问题的一部分主要是你似乎如何安排你的翻译班。请记住,对于一个实体,整个概念都基于实例身份。因此,实体的转换器不应返回新对象,它应返回对象的正确实例。这通常意味着您必须首先为其提供该实例。

根据更新与创建新对象进行思考可能很有用。

对于更新,我将构建此操作的方式如下:我将获得应用程序调用的Web服务以获取并返回合同对象。这个Web服务调用存储库和Translators来完成它的工作。验证保留在域对象上。

在代码中,更新看起来如下所示。

网络服务:

[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
        }
    }
}

译者:

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.
    }

}

这里的关键概念是因为我们有一个实体,我们获得实际的Order,实体的实例,然后使用转换器来更新属性而不是创建一个新的Order实例。因为我们正在获取原始Order,而不是创建新实例,所以我们可以假设所有关联都是由延迟加载填充或填充的。我们不必从OrderContract重新创建任何关联,因此问题就会消失。

我认为问题的另一部分可能是您对工厂设计的理解。确实,对于实体而言,工厂可能没有设置所有可能的属性 - 如果确实如此,该方法可能会变得无可救药。

但是工厂应该做的是为新对象创建所有关联,以便返回的新对象处于完整有效聚合的有效状态。然后调用者可以设置所有其他各种各样的“简单”属性。

任何时候你有一个工厂,你必须决定传递什么参数。也许在这种情况下,Web服务获得实际的客户并将其作为参数传递给工厂。或者,也许Web服务传入Id,工厂负责获取实际的Customer实例。它会因具体情况而有所不同,但无论如何,无论如何它都需要其他对象,工厂应至少返回一个完全填充的对象,就其图形而言,即所有关系都应该存在且可以穿越。

在代码中,新订单创建的可能示例可能是:

[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
        }
    }
}

那么,希望有帮助吗?