DDD富域模型和聚合根

时间:2016-10-25 10:37:34

标签: c# domain-driven-design

我正在尝试开发一个允许用户为多租户应用程序创建租户的小应用程序。 在生产服务器上,我们有2个站点实例,版本N(生产)和版本N + 1(测试生产)。

有些租户是测试租户。所以我们必须改变网站版本的租户。

当租户被分配到站点时,我需要使用API​​ Microsoft.Web.Administration配置IIS以将绑定添加到站点实例。

示例:如果我们将租户(tenant1)从生产传递到测试生产,我们必须删除网站“生产”的绑定“www.tenant1.com”并将其添加到网站“生产测试”

对于这个域,我设计了两个聚合根

public class Tenant : IEntity, IAggregateRoot
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string DbInstanceName { get; set; }
    public Site Site { get; set; }
    public virtual Icollection<Binding> Bindings { get; set; }
}

public class Binding: IEntity
{
    public int Id { get; set; }
    public int Port { get; set; }
    public string Protocol { get; set; }
    public string Adress { get; set; }
}

当我加载租户时,它的绑定被加载,网站正在加载但不是网站的绑定..

public class Site : IEntity, IAggregateRoot
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string IISiteName { get; set; }

    public virtual ICollection<Tenant> Tenants { get; set; }
    public virtual ICollection<Binding> Bindings { get; set; }
}

当我加载网站时,它的绑定被加载,租户正在加载但不是租户的绑定..

首先,在网站中有租户列表,租户中有站点,知道租户和站点是聚合是可以接受的吗?

然后我有一个给我带来问题的用例,因为允许用户编辑租户的视图包含一个包含网站的组合,当租户更新时,可以更改与网站的关联。

第一种方法:

public class TenantService {
    public void UpdateTenant(Tenant tenant, int newSiteId) {

    var currentTenant = _repoTenant.find(tenant.Id);
    var newSite = _repoSite.find(newSiteId);

    // mapping
    // ...

    if(tenant.Site != null) {
        // important: i have to load site from it's aggregate, because I KNOW (but if i was another developer i wouldn't)
        // that's site's bindings are note loaded from client's aggregate
        var currentSite = _repoSite.find(tenant.Site);
        currentSite.RemoveTenant(tenant);
        // iisadministrator, supply an abstraction to configure binding on iis
        iisadministrator.SetBinding(currentSite .IISSiteName, currentSite.Bindings);
    }

    newSite.Add(tenant);
    // iisadministrator, supply an abstraction to configure binding on iis
    iisadministrator.SetBinding(newSite.IISSiteName, newSite.Bindings);

    // saving..
}
}

public class Site : IEntity, IAggregateRoot
{
    public void AddTenant(Tenant tenant) {
        this.Tenants.Add(tenant);
        tenants.Bindings.ToList().Foreach( b => this.Bindings.Add(b));
    }

    public void RemoveTenant(Tenant tenant) {
        this.Tenants.Remove(tenant);
        tenants.Bindings.ToList().Foreach( b => this.Bindings.Remove(b));
    }               
}

这种方法有3个问题:

  • 当其他开发者向网站添加租户时,我无法确定他是否已从之前的网站中删除
  • 当开发人员将租户添加到站点时,我无法确定他是否更新了IIS上的绑定
  • 可能是建模问题:开发人员必须知道它必须完全加载当前站点(拥有所有绑定并可以更新iis) 第二个:
public class TenantService {
    public void UpdateTenant(Tenant tenant, int newSiteId) {

        var currentTenant = _repoTenant.find(tenant.Id);
        var newSite = _repoSite.find(newSiteId);

        // mapping
        // ...

        // iisadministrator, supply an abstraction to configure binding on iis
        newSite.Add(client, iisAdministrator);

        // saving.. 
    }
}

public class Site : IEntity, IAggregateRoot
{
    public void AddTenant(Tenant tenant, IISAdministrator iisadministrator) {

        if(tenant.Site == Site) return; 


        if(tenant.Site != null) tenant.Site.removeTenant(tenant); <--  all bindings are not loaded (1)

        this.Tenants.Add(tenant);
        tenants.Bindings.ToList().Foreach( b => this.Bindings.Add(b));
        iisadministrator.SetBinding(this.IISSiteName, this.Bindings);
    }

    public void RemoveTenant(Tenant tenant, IISAdministrator iisadministrator) {
        this.Tenants.Remove(tenant);
        tenants.Bindings.ToList().Foreach( b => this.Bindings.Remove(b));
        iisadministrator.SetBinding(this.IISSiteName, this.Bindings); <-- all bindings are not loadedn see(1)
    }               
}

更好但其他问题在这里:

  • 未加载所有当前站点的绑定,因此更新iis时出现问题

另外在2种情况下,如何在同一事务中更改实体和IIS的更改?

关于建模,如何选择:

tenant.setSite(Site site) { }
tenant.changeSite(Site oldSite, Site newSite) { }
site.AddTenant(Tenant tenant) { }

有没有一种方法论呢?

感谢的。

1 个答案:

答案 0 :(得分:2)

  

首先,在网站中有租户列表,租户中有站点,知道租户和站点是聚合是可以接受的吗?

不,这没有任何意义。引用列表可能有意义,具体取决于您需要每个聚合强制执行的不变量。试图将一个聚合体嵌套在另一个聚合体中表明,聚合边界出现了严重错误。

  

如何在同一事务中更改实体和IIS的更改?

好吧,您可以在寻找管理两阶段提交的方法,但通常的答案是在从更新到模型的单独事务中向您的端口发送消息。您通常放弃防止模型和远程系统失去同步的想法,而是专注于检测和缓解。

参见Udi Dahan关于reliable messaging的演讲。 “Setter”通常是幂等的,因此至少一次交付可能会产生令人满意的结果。

  

关于建模,如何选择:

tenant.setSite(Site site) { }
tenant.changeSite(Site oldSite, Site newSite) { }
site.AddTenant(Tenant tenant) { }
  

有没有一种方法论呢?

一般规则是“集合”应被视为代码气味;将业务逻辑放入汇总中,让它做出判断。如果没有做出判断 - 那么为什么数据成员会成为聚合的一部分呢?

或者,换句话说,您需要评估您的解决方案是数据库还是服务。 Borrowing from Udi again ...

  

Dahan认为服务必须具有某种功能和一些数据。如果它没有数据,那么它只是一个函数。如果它所做的只是对数据执行CRUD操作,那么它就是数据库。

如果您的解决方案没有否决更改,如果其责任仅限于记录在其他地方制定的业务决策,那么您应该考虑数据库。