持久模型到域模型映射而不暴露域对象属性

时间:2017-10-11 08:49:53

标签: java oop domain-driven-design persistence ddd-repositories

我知道这是一个常见的问题,但我找不到另一个能解决我疑虑的问题。

通常,如果项目很小,我会在表示域对象的同一对象中进行持久性注释。这允许从数据库加载实体并使所有setter保持私有,从而确保任何实例始终处于有效状态。类似的东西:

@Entity
class SomeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String attribute1;
    private String attribute2;
    private String attribute3;
    // ... other attributes

    protected SomeEntity() {}

    /* Public getters */
    public Long getId() { ... }

    public String getAttribute1() { ... }

    public String getAttribute2() {  ... }

    /* Expose some behaviour */
    public void updateAttributes(String attribute1, String attribute2) { 
       /* do some validations before updating */
    }
}

如果我想要使用不同的持久模型,我的问题就出现了。然后我会有类似的东西:

/* SomeEntity without persistent info */
class SomeEntity {
    private Long id;
    private String attribute1;
    private String attribute2;
    private String attribute3;
    // ... other attributes

    protected SomeEntity() {}

    /* Public getters */
    public Long getId() { ... }

    public String getAttribute1() { ... }

    public String getAttribute2() {  ... }

    /* Expose some behaviour */
    public void updateAttributes(String attribute1, String attribute2) { 
       /* do some validations before updating */
    }
}

和DAO:

@Entity
class SomeEntityDAO {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String attribute1;
    private String attribute2;
    private String attribute3;

    public SomeEntityDAO() {}

    /* All getters and setters */
}

我的问题是,如何在不暴露SomeEntity属性的情况下将SomeEntityDAO映射到SomeEntity?

如果我创建一个像public SomeEntity(String attribute1, String attribute2, ...) {}这样的构造函数,那么任何人都可以创建一个无效的SomeEntity实例。如果我在SomeEntity中公开所有setter,也会出现同样的情况。

我也不认为是使用updateAttributes()构建对象的有效解决方案,因为这会执行一些我此刻不想执行的验证(我们相信&#39 ; s persistet in database)。

我正在考虑让所有的setter受到保护,因此DAO可以扩展实体并且可以访问setter ......但是我不确定这是不是一个好的选择。

解决此问题的最佳或常用方法是什么?

3 个答案:

答案 0 :(得分:1)

我遇到了同样的问题。环顾四周,我发现没有解决方案。相信我,如果它存在于某处隐藏得很好。没有任何建议当你必须处理ORM实体无处不在的旧项目时该怎么做,并且在Domain和ORM模型之间迈出了一大步。

鉴于此,我已经扣除了如果你真的想要让你的Domain实体保持纯净(那么非获取和设置 - 后者我永远不会接受!)你必须做一些交易。因为在没有给实体一些额外知识的情况下,没有办法分享内部。请注意,这并不意味着您必须让Domain实体知道ORM层,也不必使用getter。只是,我已经得出结论,域实体应该有办法将它们作为一个不同的模型公开。

因此,总而言之,在您的情况下我会做的是建立一个访客模式。域实体 EntityA 将实现 EntityAVisitable 接口以接受 EntityAVisitor 或类似的内容。

interface EntityAVisitable {
   accepts(EntityAVisitor visitor);
}

构建器实现了访问者 EntityAVisitor 所需的接口。

interface EntityAVisitor<T>{
    setCombinedValue1_2(String attribute1_attribute2_combinedInEntity);
    <T> build();
}

EntityAVisitor 接口的 build()函数使用泛型类型T.这样,Domain实体就不知道具体实现的返回类型。的 EntityAVisitor

完美吗?没有。

完美的解决方案是摆脱ORM(实际上我会说我讨厌它们,因为使用的方式大部分都是错误的 - 但这是我个人的想法)。

好吗?没有。

由于语言限制(我想你使用Java),不允许使用一个很好的解决方案。

在封装域实体的真实内容方面做得好吗?是。

不仅如此,您可以通过这种方式确定可以暴露的内容以及方式。因此,在我看来,在保持实体纯洁和必须与座位下的ORM合作之间是一个很好的协议。

答案 1 :(得分:0)

域实体应该是自我验证的,这意味着它应该只根据它的内部值进行验证。如果更新需要依赖于外部依赖性的验证,那么我将创建一个负责更新的更新程序类。在updater类中,您可以使用规范模式(作为可注入依赖项)来实现验证。

修改时使用域实体,DTO用于只读投影。当您以只读方式使用直接DTO时,可以获得性能和简化收益。这用于CQRS模式。

class SomeEntity {
    private Long id;
    private String attribute1;
    private String attribute2;
    private String attribute3;
    // ... other attributes

    public SomeEntity() {}

    /* Public getters/setter */
    public Long getId() { ... }

    public String getAttribute1() { ... }

    public String getAttribute2() {  ... }

    public Long setId() { ... }

    public String setAttribute1() { ... }

    public String setAttribute2() {  ... }
}

//classes/interfaces named for clarity
class EntityUpdater implements IEntityUpdater {
  public EntityUpdater (ISpecification spec){
  }

  public updateEntity(SomeEntity entity){
    //assert/execute validation
  }
}

答案 2 :(得分:0)

某些ORM允许通过字段访问设置实体值(与setter方法相反)。

JPA使用@Access注释。见What is the purpose of AccessType.FIELD, AccessType.PROPERTY and @Access

我创建了一个可以使用字段访问的ORM sormula。请参阅@Row fieldAccess和测试用例org.sormula.tests.fieldaccess。