存储库的职责应该有多限制?

时间:2012-03-11 05:48:48

标签: oop architecture domain-driven-design data-access-layer

有些阅读告诉我,我的存储库(1)应该什么也不做,只能通过配置的(注入的)数据提供程序直接进行CRUD操作,但是在小项目中我经常发现添加整个服务层也有点多。

示例,我的应用程序处理'银行'分支机构和客户。现在,如果我的用户为每个工作会话“打开”一个分支,并且每个新客户端都被分配到该分支,我倾向于注入我编写的AppContext单例,以跟踪环境应用程序属性,例如当前打开的分支,进入我的ClientRepository并让该对象将正确的BranchID分配给新的客户端记录。

我知道这在最纯粹的意义上是不正确的,但我在这里谈论基本的2层应用,UI和数据。唯一的业务“逻辑”确实是在查询数据库时完成的。我可以在这里使用更合适的模式吗?

(1)The Repository Pattern

2 个答案:

答案 0 :(得分:1)

我个人同意,存储库应尽可能“干净”,如第一段所述。

根据经验,我还通过在CRUD函数中编写业务逻辑代码来“欺骗”,这通常需要其他存储库作为属性。

在ASP.net工作,我利用了泛型和事件处理。

public class Repository<T, DC>
        where T : class
        where DC : DataContext, new()
{
    public delegate void RecordInsertedHandler(object s, BasicEventArgs<T> e);
    public event RecordInsertedHandler RecordInserted;

    public delegate void RecordDeletedHandler(object s, BasicEventArgs<T> e);
    public event RecordDeletedHandler RecordDeleted;

    public delegate void RecordObtainedHandler(object s, BasicEventArgs<T> e);
    public event RecordObtainedHandler RecordObtained;

    public delegate void RecordUpdatedHandler(object s, BasicEventArgs<T> e);
    public event RecordUpdatedHandler RecordUpdated;

    protected DC dc;

    public Repository(string connectionString="")
    {
        dc = ((connectionString == null) || (connectionString == "")) ? new DC() :   DynamicTypes.Instantiate<DC>(connectionString);  // See code below for DynamicTypes:

    }

    // There are similar functions for other events not shown.
    protected void OnRecordInserted(BasicEventArgs<T> obj)
    {
        if (RecordInserted != null)
        {
            RecordInserted(this, obj);
        }
    }

    // Only the Insert is shown here.
    private void Insert(T obj)
    {
        dc.GetTable<T>().InsertOnSubmit(obj);
        dc.SubmitChanges();

        OnRecordInserted(new BasicEventArgs<T>(obj));
    }
}

基本思想是您可以在常规CRUD函数的正确位置触发/调用这些事件。我假设您的存储库将是ASP.net页面或Windows窗体的成员。那些容器将成为“事件监听器”,因为它们可以操纵UI。

动态类型:

public static class DynamicTypes
{        
    public static T Instantiate<T>(params object[] args)
    {
        return (T)Activator.CreateInstance(typeof(T), args);
    }
}

BasicEventArgs:

public class BasicEventArgs<T> : EventArgs
{
    private T _Data;
    private string _Message;

    public BasicEventArgs(T data, string message="") : base()
    {
        _Data = data;
        _Message = message;
    }

    public T Data
    {
        get { return _Data; }
        set { _Data = value; }
    }

    public string Message
    {
        get { return _Message; }
        set { _Message = value; }
    }
}

存储库:

public class BranceshRepository : Repository<Branch, YourDataContext>
{
     public BranchesRepository(string connectionString="") : base(connectionString)
     {
     }
}

public class ClientsRepository : Repository<Client, YourDataContext>
{
     public ClientsRepository(string connectionString="") : base(connectionString)
     {
     }
}

现在,例如,我们有一个ASP.net页面:

public partial class MyPage : Page
{
      protected ClientsRepository _ClientsRepository;
      protected BranchesRepository _BranchesRepository;

      protected void Page_Load(object s, EventArgs e);
      {
           _ClientsRepository = new ClientsRepository(...);
           _BranchesRepository = new BranchesRepository(...);

           _BranchesRepository.RecordInserted += new Repository<Branch,YourDataContext>.RecordInsertedHandler(OnBranchInserted);
           _ClientsRepository.RecordInserted += new RepositoryM<Client, YourDataContext>.RecordInsertedHandler(OnClientInserted);
      }

      protected void OnBranchInserted(object s, BasicEventArgs<Branch> e)
      {
             /* e.Data is your newly-inserted branch with the newly-generated Id
                from the database. You may save this branch to Session for
                later use when your user inserts a new client.
               */
      }

      protected void OnClientInserted(object s, BasicEventArgs<Client> e)
      {
              Branch currentBranch = (Branch)Session["Branch"];
              e.Data.BranchId = currentBranch.Id;
              _ClientsRepository.Update(e.Data);                
      }

      // Control event handlers not shown, like CreateClient_BT_Click, for example.
}

这样,您无需在ClientsRepository中注入单个单元,并且您可以在这些事件处理程序中完全访问您的UI。

答案 1 :(得分:1)

在我们的团队中,我们追求(至少约3年或4年)这个完美的世界理念,所有业务逻辑必须完全从业务逻辑中移除。所以我们建立了很多服务。

我们相信,在这个完美的世界里,如果你有一个特定的范围,你就可以为它创造一种服务。正如Mickael之前提到的“ClientsRepository”将为客户端提供SCRUD(s - search),我们只称它为ClientsService(在Service中告诉您有关客户端的所有信息)。在这里你有威胁,当一个不知情的开发人员或者在极端压力或截止日期之前看到这样的代码:

public ModifyClientResponse ModifyClient(ModifyClientRequest request){

    ClientEntity clientEntity=new ClientEntity();
    ModifyClientResponse clientResponse = new ModifyClientResponse();

    ClientMapper mapper=new ClientMapper();
    mapper.Map(request.Client, clientEntity);

    clientEntity.Update();

    ClientDTO responseClientDto=new ClientDTO();
    mapper.Map(clientEntity, responseClient);

    clientResponse.Client=responseClientDto;

    return clientResponse
}

我认为,这非常简单。同样的开发人员得到一个任务,做一些事情,比如“当客户发货地址更新时,我们需要在尚未批准或发货的发票中更改他的地址数据”,它将变成这样:

public ModifyClientResponse ModifyClient(ModifyClientRequest request){

    ClientEntity clientEntity=new ClientEntity();
    ModifyClientResponse clientResponse = new ModifyClientResponse();

    ClientMapper mapper=new ClientMapper();
    mapper.Map(request.Client, clientEntity);

    clientEntity.Update();

    if(clientEntity.Address.DataModification==DataModification.Modified){
    // all the silly business logics about address being changed
    }

    ClientDTO responseClientDto=new ClientDTO();
    mapper.Map(clientEntity, responseClient);

    clientResponse.Client=responseClientDto;

    return clientResponse
}

现在没有人责怪开发人员,而且规模小,效率足够好。但从长远来看,它将成为一场灾难。总是变化的需求将越来越难以维护,基本上我们认为它是针对SOA和大多数SOLID开发原则的。它削弱了服务的单一责任 - 各种不好的东西。

然而,我想再回过头来看,这应该发生在“完美的世界”中:我们所处的上述代码是通过向客户DTO发送消息来处理的。因此,Invoice服务可以连接到“客户端修改”并使用处理程序,进行适当的处​​理。通过这种方式,我们可以将客户分离 - 他们可以在其他数据源/服务器/星球/其他任何需要的地方。

我们还尝试保持这个更干净,没有自定义消息处理程序,但使用工作流程。我们有一个内部工作流引擎(几乎像Windows工作流基础或jboss),它可以根据某些事件订阅和触发工作流。

目前,我们正在研究创建ECA规则引擎(http://en.wikipedia.org/wiki/Event_condition_action),这将使其更加灵活。但对我们来说足够了:))

如果我要实现它,你的任务将是一个仅执行CRUD并触发消息的服务以及处理这些消息并执行业务逻辑的另一个服务。但这将是一个完美的世界方法,可能对此的估计会吓到某人。而且我会以最简单的方式做到这一点,即通过将所有内容结合在一起,然后扼杀良好代码的基本原则,只是因为如果这个系统突然长大成开放和关闭银行的大规模产品,它就不那么痛苦了。分支。

简而言之:我是agree,存储库应该是清晰的,除了数据之外别无其他。