【问题标题】:Look for a design pattern for managing an operation that needs to update data in multiple datasources寻找一种设计模式来管理需要更新多个数据源中的数据的操作
【发布时间】:2018-05-18 20:42:46
【问题描述】:

我有一个带有后期操作的 api。我们使用 C# 并为每一层(Api、业务逻辑、数据访问)提供单独的程序集

此操作必须与多个数据源交互。

  1. 检查数据源1中是否已经存在数据
  2. 如果数据源1中不存在,则检查数据源2中是否已经存在数据
  3. 如果数据源2中不存在,先插入数据源1再插入数据源2

我们定期使用服务(用于逻辑)和存储库进行数据访问。

选项 1

  • 为每个数据源创建一个存储库,每个数据源都有一个获取操作和一个插入操作。
  • 创建一个使用两个存储库并遵循上述逻辑的服务。

优点是所有的逻辑都在服务中,感觉有点“单一职责”。这样做的缺点是以后有人可以直接使用“数据源 1 存储库”并插入一条记录,而不知道它也应该插入到数据源 2 中。

选项 2 使用与两个数据源交互的单个获取和插入操作创建一个存储库。这将确保您无法在没有其他人的情况下与一个人互动,但感觉不像“单一职责”。

选项 3 有 3 个存储库....

  • 数据源 1 的 1 个存储库(但只有一个内部接口,因此不能被其他程序集使用)
  • 数据源 2 的 1 个存储库(但只有一个内部接口,因此不能被其他程序集使用)
  • 1 个具有公共接口的存储库,可由服务使用(在不同的程序集中)

公共存储库可以使用 2 个“特定于数据源”的存储库并遵循上述逻辑。这将逻辑从服务转移到存储库,但其优点是实现对数据层之外的任何人都隐藏。

人们的想法是什么?有这方面的设计模式吗?

【问题讨论】:

  • 数据源是否具有相同的结构和架构?
  • We regularly make sure of services and repositories - 我不明白这是什么意思,你能澄清一下吗?
  • 嗨@CodingYoshi - 数据源不相同。数据源 1 用于全新系统。数据源位于旧数据库中,并保存数据源 1 的密钥以及一些附加值。
  • @CraigBroadman 您仍然可以在我的回答中使用我的解决方案。数据结构是否相同。
  • @ConstantinGalbenu 这是一个错字 - 意思是说我们经常使用 - 我已经更新了

标签: c# design-patterns domain-driven-design repository-pattern solid-principles


【解决方案1】:

一方面,我可以为每个数据源创建一个存储库,每个数据源都有一个获取操作和一个插入操作。然后我可以创建一个遵循上述逻辑的服务。

优点是所有的逻辑都在服务中,感觉有点“单一职责”。

我也喜欢这个选项,所以如果我是你,我会选择这个选项。

另一种选择是创建单个存储库,其中包含与两个数据源交互的单个插入操作。这将确保您无法在没有其他人的情况下与一个人互动,但感觉不像是“单一职责”。

我不喜欢这种将数据源检查放在一个 repo 中的选项,因为这听起来像是服务层的责任。

另一种选择是拥有 3 个存储库。 1 是公共的,由服务 (DDD) 使用,并且此存储库与仅内部的数据源特定存储库对话。

我不确定您所说的仅限内部是什么意思。如果内部是指internal,就像在程序集中一样,它仍然具有与前一个选项相同的缺点。

关于第一个选项,我和你都喜欢,你这样说:

这样做的缺点是有人可以很容易地在以后出现并直接使用“数据源 1 存储库”并插入一条记录,而不知道它也应该插入到数据源 2 中。

我们可以做一些事情来避免这种情况。创建一个这样的类并想象我们正在保存Customer

public class Customer { }

public class CustomerData
{
    public string MyProperty { get; private set; }

    private CustomerData()
    {
    }

    public Customer Customer { get; private set; }

    // or not void
    public static void Save(Customer customer)
    {
        // Check all datasources and save wherever you need to 
        // obviously through injection or whatever. I will just
        // new-it up here
        var cd = new CustomerData { Customer = customer };
        var repo1 = new CustomerRepository();
        repo1.Save(cd);

        bool someCondition = true;
        if (someCondition)
        {
            var repo2 = new CustomerRepository();
            repo2.Save(cd);
        }
    }
}

请注意构造函数是私有的,所以开发者不能创建它。现在像这样创建您的存储库:

public class CustomerRepository
{
    public void Save(CustomerData cd) { // Save cd.Customer to datasource }
}

请注意它需要CustomerData 作为参数。

好的!所以让我们使用 repo:

var repo = new CustomerRepository();
// Oops cannot create CustomerData. 
// repo has no other method except for CustomerData.
repo.Save(new CustomerData()); 

// There is no other way except for this or the developer has to add 
// a method to the repo-but what can we do about that, not much!
CustomerData.Save(new Customer());

【讨论】:

  • 我已经用一些额外的细节更新了我的帖子。虽然使用私有构造函数会限制开发人员创建对象的实例,但这似乎很 hacky。我喜欢遵循 ​​SOLID 原则,总是使用依赖注入。
  • @CraigBroadman 请解释为什么它是 hacky 以及为什么它不遵循 SOLID?
  • 经过反思,我认为选项 1 可能是最好的选择。似乎没有专门适合这种情况的设计模式。感谢大家的cmets
【解决方案2】:

这个需求感觉很像Chain of Responsibility Pattern,不幸的是,这对减少数据访问代码量没有太大作用。我很想在检查中每个“级别”都有一个阅读器,让它们都实现一些通用接口,并使用责任链(即我可以处理这个,如果不询问链中的下一个对象)来做操作。您可以使链中的每个项目成为一个迷你存储库,或者在需要时使用另一个存储库。

如果您已经在结构中拥有特定的存储库,或者在其他地方需要类似的阅读器,也许只需要担心它们。

【讨论】:

    【解决方案3】:

    我为这些愚蠢的图纸道歉,但我试图将您的工作流程表示为有限自动机。

    所以,我想我们可以像这样减少它。

    并且只有一个有效的数据源。

    此外,如果需要,请考虑同步机制,这可能会导致开销。

    【讨论】:

      【解决方案4】:

      据我所知,界面类似于persistSomethingToRepository。这个接口是所有客户端代码关心的。它不关心您使用辅助遗留系统的事实(也不应该)。这个观察大大简化了实现,因为您可以使用Decorator pattern 来装饰 DataSource1 并持久化到 DataSource2。

      我(还)没有足够的 C# 经验,但伪 OOP 代码是这样的:

      interface Repository {
          void persistSomething(something);
      }
      
      class Source1 implements Repository {
          void persistSomething(something) {
              // insert into database
         }
      }
      
      class Source2AndSource1 implements Repository {
          // inject the right database connection and a Source1 instance
          void persistSomething(something) {
              // Check if the data already exists in datasource 1
              // If it doesn't exist in datasource 1, check if the data already exists in datasource  2
             //If it doesn't exist in datasource 2, insert it in to datasource 1 and then insert it in to datasource 2
         }
      }
      

      然后,您只需将依赖注入容器 (DIC) 配置为在需要 Repository 时使用 Source2AndSource1 的实例。

      当您的旧系统(源 2)不再使用时,您只需将 DIC 配置为使用 Source1 的实例而不是 Source2AndSource1,无需对客户端代码进行任何修改。这将为您提供SOLID 代码,更准确地说是Open/closed PrincipleSingle responsibility principle

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-01
        • 1970-01-01
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多