【问题标题】:C# Service Layer Design PatternC# 服务层设计模式
【发布时间】:2015-07-09 19:59:59
【问题描述】:

我们正在研究创建一个新项目,并希望探索使用存储库和服务层模式,目的是创建可使用模拟存储库完全测试的松散耦合代码。

请看下面的基本架构思想。我们将使用接口来描述存储库并将它们注入服务层以删除任何依赖项。然后使用 autofac 我们将在运行时连接服务。

public interface IOrderRepository
{
    IQueryable<Order> GetAll();
}

public class OrderRepository : IOrderRepository
{
    public IQueryable<Order> GetAll()
    {
        return new List<Order>().AsQueryable();
    }
}

public class OrderService
{
    private readonly IOrderRepository _orderRepository;

    public OrderService(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public IQueryable<Order> GetAll()
    {
        return _orderRepository.GetAll();
    }
}

public class EmailService
{
    public void SendEmails()
    {
        // How do I call the GetAll method from the order serivce
        // I need to inject into the orderService the repository to use
    }
}

有几个问题我们无法找出最佳的前进方向。

1) 服务是否应该重现 CRUD 方法,因为看起来我们可能正在重现代码而没有真正的好处。还是 UI 应该直接调用存储库?

2) 当一个服务需要调用另一个服务时会发生什么。在上面的示例中,如果电子邮件服务需要获取所有订单,我们是否将订单服务注入到电子邮件服务中?

希望这是有道理的

【问题讨论】:

  • Email 服务不应该知道 OrderService 之类的服务,你需要 Mediator 与 Email&&Order 服务一起工作,这样它们才能解耦
  • 你们的订单服务是做什么的?只封装 OrderRepository?或者它做的不止这些,从你的代码看起来它只是一个冗余层。
  • 我会问另一个问题。在存储库边界之外公开 IQueryable 是否很好?对我来说,不是。这是一个有漏洞的抽象。
  • @ThomasJaskula IQueryable 完全可以模拟吗?
  • @gideon 是的,但这不是重点

标签: c# design-patterns service tdd repository


【解决方案1】:

电子邮件服务不应该知道诸如 OrderService 之类的服务,您需要 Mediator 与 Email&&Order 服务一起工作,以便将它们解耦,或者 AdapterIOrder 调整为 IEmail

IEnumerable<IOrder> orders = orderService.GetAll();

// TODO: Create emails from orders by using OrderToEmailAdaptor
IEnumerable<IEmail> emails = ... 
emailService.SendEmails(emails);

public sealed class EmailService
{
    public void SendEmails(IEnumerable<IEmail> emails)
    {
    }
}

调解员

定义一个对象,该对象封装一组对象如何交互。 中介者通过阻止对象引用来促进松散耦合 彼此明确,它可以让你改变他们的互动 独立

适配器

适配器模式(通常称为包装模式或 只是一个包装器)是一种翻译一个接口的设计模式 为一个类转换成一个兼容的接口

【讨论】:

  • +1 by mediator 我以为您指的是中介模式,但这是正确且简单的! :) 我认为不需要订单服务,存储库已经负责解耦如何 检索订单?
  • 您好,感谢您的反馈。在上面的示例中,谁会调用 SendEmails 方法?您是否还需要在某个地方获取订单然后调用电子邮件服务的代码?
  • @user1223218 :这可能是一个中介。考虑到您的需求,谁来确定何时调用SendEmails(),是不是某个事件处理程序?
  • 谢谢,您对重现代码以使服务层将 CRUD 命令传递给存储库有何想法?如果觉得多余,但同时对未来的改变持开放态度。
  • @user1223218:见下面的讨论CRUD Interface for a Service - Is a bad practice ?
【解决方案2】:

看看领域驱动设计。 DDD 将大部分逻辑移入实体(OrderEmail)并让它们使用存储库。

1) 服务是否应该重现 CRUD 方法,因为看起来我们可能正在重现代码而没有真正的好处。还是 UI 应该直接调用存储库?

当您发现自己在实体之外编写业务逻辑时,会使用 DDD 中的服务。

2) 当一个服务需要调用另一个服务时会发生什么。在我们上面的例子中,如果 Email Service 需要获取所有的订单,我们是否将 order 服务注入到 Email 服务中?

在构造函数中注入它。但是,订单服务应该发送电子邮件,而不是相反。

DDD 方法是创建一个OrderNotificationService,它接受域事件OrderCreated 并编写一封电子邮件,并通过EmailService 发送电子邮件

更新

你误会了我。重复的逻辑永远不会是好的。当我的存储库有一个方法时,我不会在我的服务中放置一个名为 GetAll 的方法。我也不会将该方法放在我的实体中。

示例代码:

var order = repository.Create(userId);
order.Add(articleId, 2);  
order.Save(); // uses the repository

Order.Send() 应该创建一个OrderNotificationService 可以捕获的域事件。

更新2

Repository.Create 只是一种工厂方法(google factory method pattern),可以将所有域模型创建集中在一个地方。它在数据库中不做任何事情(尽管在未来的版本中可以)。

至于 order.Save 它将使用存储库来保存所有订单行、订单本身或其他任何需要的东西。

【讨论】:

  • 您好,感谢您的回复。如果 order 有像 Order.GetAll() 这样的方法,这不是耦合的,order 怎么知道要使用哪个存储库?谢谢
  • +1 - OrderNotificationService 对我来说绝对是正确的方法。
  • 您好,repository.Create 是否返回订单?如果是这样 order.Save 如何使用存储库。感谢您的帮助。
【解决方案3】:

您可以使用适配器模式或使用 DI 工具

public class EmailService
{
    private IOrderRepository _orderservice = null;

    public EmailService(IOrderService orderservice)
    {
        _orderservice = orderservice;
    }

    public void SendEmails()
    {
        _orderService.GetAll();
    }
}

【讨论】:

    【解决方案4】:

    我会做的是

    1. 不直接从 UI 调用存储库,而是调用服务,服务应该使用存储库,

    2. 我会从电子邮件服务调用订单存储库的方法,这样我只需将 OrderRepository 注入电子邮件服务(不是订单服务)

    【讨论】:

    • 感谢您的回复,如果电子邮件服务直接使用存储库,我们不会冒发生不同操作的风险,因为 GetAll() 可能会在服务中更改为与 GetAll() 不同的工作方式存储库。谢谢
    猜你喜欢
    • 2012-11-26
    • 2015-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 2017-01-31
    相关资源
    最近更新 更多