如何仅公开对存储库的内部域访问

时间:2019-06-18 06:28:05

标签: c# architecture domain-driven-design ddd-repositories

让我们考虑这个简化的模型:

订阅类:

public class Subscription
{
    public string Name { get; }
    public ReadOnlyCollection<Subscriber> Subscribers => _subscribers.AsReadOnly();

    private readonly List<Subscriber> _subscribers;

    public Subscription(string name)
    {
        Name = name;
        _subscribers = new List<Subscriber>();
    }

    public Subscriber AddRecipient(Recipient recipient, ReceivingMethod receivingMethod)
    {
        var subscriber = new Subscriber(this, receivingMethod, recipient);
        _subscribers.Add(subscriber);
        return subscriber;
    }

    internal bool RemoveSubscriber(Subscriber subscriber)
        => _subscribers.Contains(subscriber) && _subscribers.Remove(subscriber);
}

收件人类别:

public class Recipient
{
    public Guid Id { get; }
    public string Address { get; set; }

    public Recipient(string address) : this(Guid.NewGuid(), address)
    {
    }

    internal Recipient(Guid id, string address)
    {
        Id = id;
        Address = address;
    }

    public Subscriber Subscribe(Subscription subscription, ReceivingMethod receivingMethod) 
        => subscription.AddRecipient(this, receivingMethod);
}

和订阅者

public class Subscriber : Recipient
{
    public Subscription Subscription { get; set; }
    public ReceivingMethod ReceivingMethod { get; set; }

    internal Subscriber(Subscription subscription, ReceivingMethod method, Recipient recipient)
        : base(recipient.Id, recipient.Address)
    {
        Subscription = subscription;
        ReceivingMethod = method;
    }

    public bool Unsubscribe() 
        => Subscription != null && Subscription.RemoveSubscriber(this);
}

Subscriber的结果是Recipient订阅了Subscription,因此该对象的实例化在内部被预言。此时,我需要从存储库中加载和填充现有的Subscriber,该实现位于不同的命名空间(.Infrastructure)中,并且由于其保护级别而无法访问域的内部

我努力寻找正确的方法。我考虑过将基础结构层作为 friend 添加到并允许内部访问,但这将使依赖于基础架构,但我不希望它独立。现在, domain 拥有存储库接口,我可以添加这些接口的抽象实现,其中包含对模型的访问权限,并需要其他实现和持久化上下文的注入,但这并不对。

有人可以解释一下在富域模型中通常是如何完成的。

  

P.S .:这是应用程序级别的体系结构问题,因此我认为它最适合SO。

3 个答案:

答案 0 :(得分:2)

正如@maxdoxdev所说:宁可使用公共构造函数来定义域类也可能没有多大问题。

如果您确实确定不想使用 公共构造函数,则可以在相关类上选择公共工厂方法,或在<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="0dp" android:layout_height="0dp" android:src="@drawable/src" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" android:src="@drawable/src_2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <View android:id="@+id/overlay" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:background="@color/overlay_color" /> <!--Add your progress there--> </androidx.constraintlayout.widget.ConstraintLayout> 方法中使用基元来使用该方法本身在内部实例化所需的对象。

答案 1 :(得分:1)

我觉得将Domain模型暴露给存储库以及洋葱体系结构的其他层没有错。否则这是错误的(将其他层暴露给Domain)。

此外-如果您的域模型被很好地封装,并且类正在保护自己以任何不正确的状态创建(或放置),则阻止对那些类的访问似乎毫无意义,因为在任何地方实例化它们都不会发生错误只要应用程序的那部分具有足够的信息来创建这些对象,就可以在应用程序中使用该对象。

洋葱体系结构允许对洋葱内部的依赖(因此对域的依赖)。

请参考该图片: https://www.codeguru.com/imagesvr_ce/2236/Onion1.png

或全文:https://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/understanding-onion-architecture.html

将Domain暴露给洋葱体系结构的外层为您提供了一些可能性,例如实现CQRS模式,仍在Domain内部维护查询,命令-从而将验证保留在一个地方等。

编辑:

我经常使用的另一件事是应用程序层,它是所有依赖项的协调器,也是公共API的持有者。

应用程序持有存储库,基础结构和其他外部依赖项的接口。这些接口在各个层中实现,并与IoC一起注入到持久性(UI)层中。

这为您提供了从外部层替换实现的灵活性,而应用层仅在抽象层上进行中继,而根本不涉及应用层。

示例:

控制器-接受DTO并将其映射到查询或命令

Application-通过调用外部层的抽象和Domain的实际实现来处理查询或命令

域-具有丰富的模型,它们知道如何进行业务操作

存储库-只是数据访问的实现

看看这个GitHub: https://github.com/matthewrenze/clean-architecture-demo 如果您有兴趣,那也与精彩的Pluralsight视频有关。

答案 2 :(得分:0)

为了完整起见,我将对自己的问题添加一个答案,同时将其引向正确的方向,但如果给出的示例缺乏具体性,以防其他人对其他方法/具体实现感兴趣。

由于SubscriberRecipient订阅特定Subscription的产物,此处是聚合根,因此最初负责创建Subscriber,它仍然会这样做,但除此之外,我还公开了Subscriber的构造函数,以便允许添加已加载的实体。

Subscriber公众的构造函数向我介绍了挑战,以确保新的Subscriber处于有效状态。我的意思是Subscriber也指向Subscription,因为Subscription Subscribers 和其他元素的集合中也包含Subscriber Subscription创建之前处理过的依赖项。最后的解决方案(最后)似乎很简单,其方法是添加一个内部方法,将Subscriber添加到Subscription Subscribers 中,并应用其他规则,以前只能通过“订阅” Recipient来获得。

因此,我丰富了Subscription类:

internal void AddSubscriber(Subscriber subscriber)
{
    if (_subscribers.Contains(subscriber)) return;

    _subscribers.Add(subscriber);
    subscriber.Subscription = this;
}

并更改了Subscriber构造函数:

public Subscriber(Subscription subscription, ReceivingMethod receivingMethod, Recipient recipient)
    : base(recipient.EMailAdress, recipient.FirstName, recipient.LastName, recipient.Salutation)
{
    Subscription = subscription;
    ReceivingMethod = receivingMethod;
    subscription.AddSubscriber(this);
}

现在,存储库能够从已加载的持久性模型中实例化Subscribers

  

我仍然愿意接受更好的方法和/或这种方法的缺点的详细信息。