C# 服务层设计模式

13
我们正在考虑创建一个新项目,并希望探索使用仓库和服务层模式,旨在创建松耦合的代码,使用模拟仓库可以进行完全可测试。请参见以下基本架构想法。我们将使用接口来描述存储库,并将其注入到服务层中以消除任何依赖关系。然后使用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)当一个服务需要调用另一个服务时会发生什么。在上面的例子中,如果电子邮件服务需要获取所有订单,我们是否将订单服务注入到电子邮件服务中?

希望这样说得清楚。


电子邮件服务不应该知道OrderService等服务,您需要中介者来处理电子邮件和订单服务,以便它们解耦。 - sll
你们的订单服务是做什么的?仅仅封装了OrderRepository吗?还是除此之外还有其他功能?从你们代码看来,它似乎只是多余的一层。 - gideon
1
我想问另一个问题。将 IQueryable<T> 暴露在存储库边界之外是否好? 对于我来说,不是。这是一个泄漏的抽象。 - Tomasz Jaskuλa
@ThomasJaskula IQueryable<T>是完全可模拟的吗? - gideon
@gideon 是的,但这不是重点。 - Tomasz Jaskuλa
显示剩余2条评论
4个回答

6

邮件服务不应该知道OrderService等服务的存在,需要使用中介者模式来处理邮件和订单服务,以实现它们的解耦,或使用适配器模式来将IOrder适配到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
+1 通过中介者,我一开始以为你是在提中介者模式,但这个方法正确而简单! :) 我认为不需要一个订单服务,因为存储库已经处理了解耦订单的检索方式。 - gideon
嗨,感谢您的反馈。在上面的示例中,谁会调用SendEmails方法?难道你还需要在某个地方编写代码来获取订单,然后调用电子邮件服务吗? - user1223218
@user1223218:这可能是一个中介者。考虑到您的要求,谁决定何时调用“SendEmails()”呢?是某个事件处理程序吗? - sll
谢谢,您对于复制代码以便服务层将CRUD命令传递给存储库有何想法?这似乎是多余的,但同时在未来更加开放。 - user1223218
@user1223218:请查看以下讨论CRUD接口服务-是一种不好的实践吗? - sll

3
请看领域驱动设计(Domain Driven Design)。DDD将大部分逻辑放入实体(OrderEmail),并让它们使用存储库。

1)服务是否应该重复CRUD方法,因为这样似乎会复制没有真正好处的代码。还是UI直接调用存储库?

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

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

在构造函数中注入。但是,订单服务应发送电子邮件,而不是相反。
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只是一个工厂方法(搜索“工厂方法模式”),用于将所有领域模型的创建集中在一起。它不会在数据库中执行任何操作(尽管在将来的版本中可能会执行)。

至于order.Save,它将使用存储库保存所有订单行、订单本身或其他需要保存的内容。


嗨,感谢您的回复。如果Order类有像Order.GetAll()这样的方法,那么它不就耦合了吗?Order如何知道使用哪个存储库呢?谢谢。 - user1223218
+1 - OrderNotificationService 对我来说确实更像是正确的方式。 - james lewis
嗨,repository.Create是否返回订单?如果是,order.Save如何使用repository呢?感谢您的帮助。 - user1223218

1
你可以使用适配器模式或依赖注入工具。
public class EmailService
{
    private IOrderRepository _orderservice = null;

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

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

0

我会这样做:

  1. 不直接从UI调用repositories,而是调用service,让service使用repository。

  2. 我会从Email service中调用Order repository的方法,这样我只需要将OrderRepository注入到Email Service中(而不是Order Service)。


谢谢您的回复,如果电子邮件服务直接使用存储库,我们难道不会面临不同的操作风险吗?因为服务中的GetAll()可能会被更改以与存储库中的GetAll()不同。谢谢。 - user1223218

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接