依赖于其他代码库的代码库

54
最近我一直在阅读SOLID原则,决定看一下我所使用的代码库如何比较。我们的某些代码中有一个存储库(repository A)。当从存储库A删除记录时,我们还需要从存储库B中删除相关记录。原始编码者因此创建了对存储库B的具体实现的依赖。存储库A中的方法在事务内,并删除来自存储库A的记录,然后调用存储库B上的方法以删除关联数据。我对S原则的理解是每个对象应该只有一个变化的原因,但是对于我的存储库A来说,它有两个变化的原因吗?或者我完全错了?
3个回答

95

代码库应该具有单一职责 - 持久化一种实体,例如员工。如果您必须从其他存储库中删除某些关联记录,则它看起来像业务逻辑。

当雇员被解雇时,我们应该删除他的工作日志。

通常业务逻辑的地方是领域服务。此服务将拥有两个存储库并完成所有工作:

staffService.Fire(employee)

实现将会看起来像这样

public class StaffService
{
    private IEmployeeRepository employeeRepository;
    private IWorkLogRepository workLogRepository;
    private IUnitOfWorkFactory uowFactory;

    // inject dependencies

    public void Fire(Employee employee)
    {
        using(var uow = uowFactory.SartNew())
        {
            workLogRepository.DeleteByEmployee(employee.Id);
            employeeRepository.Delete(employee.Id);
            uow.Commit();
        }
    }
}

因此,基本建议:

  • 尝试将业务逻辑集中在一个地方,不要将其分散到UI、存储库和数据库的不同部分(有时由于性能问题,您必须在数据库端执行一些逻辑,但这是个例外)
  • 永远不要让存储库引用其他存储库,存储库是应用程序中非常低级别的组件,具有非常简单的职责

您可能会想知道,如果您有一个员工对象,而它有一些嵌套对象存储在不同的数据库表中,该怎么办。如果您与员工分开使用该对象,则一切如上所述-您应该有单独的存储库和一些其他对象(服务),它们操作两个存储库。但是,如果您不与员工分开使用该嵌套对象,则员工是聚合根,您应该只有一个员工存储库,该存储库将查询两个表内的内容。


1
太棒了,@Sergey,这证实了我的想法。我和系统架构师交谈过,他建议这是业务层或数据层之间的一个微妙界限,但由于它正在清理数据,因此将其放在数据层中是可以的。希望我能说服他将其移至业务层。 - MrGrumpy
删除规则通常应该允许最终一致性而不是强一致性。此外,物理删除实际上相当罕见。 employee.fire() -> EmployeeFired(employeedId) -> EventSubscriber -> .... - plalx
21
仓库不应引用另一个仓库?也许是这样,但假设我有一个始终需要嵌套ClassB的ClassA(需要才能工作),但是ClassB对象本身可以单独工作。当我的服务从repositoryA检索ClassA对象时,该存储库将需要使用repositoryB来检索objB并将其分配给objA(使用工厂),如下所示:class RepositoryA { public ClassA GetById(string id) { objB = repositoryB.GetById(idB); factory.CreateObjA(idA, objB); } }如何处理以避免仓库之间的引用? - Jonathan
我的情况与@Jonathan所描述的完全相同。在这种情况下,你有什么建议? - Rod
3
@Jonathan,我认为在其他存储库中使用存储库以正确构建域实体并没有任何问题。例如,如果您有一个会议实体,无法在没有会议类型实体的情况下构建,则会议存储库使用MeetingType存储库是完全可以的。事实上,这仍然保持隔离并避免代码重复。 - L-Four
你应该在仓储中使用数据库上下文来访问ClassB,或者使用ORM将数据库表映射到应用程序实体,从而可以通过属性访问器映射表关系并访问其子项,由ORM映射工具处理。 - Machado

0
尽管这是一个老问题,但我认为这个答案可能是对@Sergey答案的补充。
对于@jonathan提到的情况,我认为我们可以使用一个工厂(可能是他已经用来创建objA的同一个工厂),负责获取objB,如下所示:
class RepositoryA {
    private final Factory factory;

    public RepositoryA(Factory factory) {
        this.factory = factory;
    }

    public ClassA GetById(string id) {
        ClassB objB = factory.GetObjBById(id);
        return factory.CreateObjA(id, objB);
    }
}

class Factory {
    public ClassB GetObjBById(string id) {
        // ...
    }

    public ClassA CreateObjA(string id, ClassB objB) {
        // ...
    }
}

这里的主要优点是它将RepositoryA类与RepositoryB类解耦。(因此,我们保持了S原则,使代码更模块化和可重用)。
此外,它使得对RepositoryA类进行测试更加容易,因为我们可以模拟Factory类,同时也更容易更改ClassB对象的实现而不影响RepositoryA类。

-4
在这种情况下,您应该利用event dispatcher模式。
在对RepoA进行删除操作后,您可以发送如下事件:
dispatch repositoryA.deleted(RecordA)

这将保存已删除记录的信息。

然后,一个事件监听器将订阅此类事件,并且作为依赖项具有存储库B,然后调用删除操作。

让我们使用B作为实体名称,监听器声明应该如下所示:

Listen RepositoryA.delete and invoke onDelete(Event)

通过这种方法,您已经实现了repoArepoB之间的松耦合(强制执行开放/关闭原则-在关闭方面),因此repoA现在又有了。

敬礼。


我进行了反对投票,因为这是明显的过度工程的例子。该解决方案拉取不必要的事件,并且问题可以用更简单的方法解决。 - Hop hop

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