混淆:Domain Model vs Entity / @ Transcient vs EntityWrapper

时间:2013-08-14 16:08:55

标签: spring hibernate spring-mvc domain-driven-design

我的网络应用程序有3层设计

数据层 - >服务层 - >介绍

我的网络应用程序使用这些框架 1)Spring MVC 2)春天 3)Hibernate(Spring Repository)

问题/困境声明

假设我有PersonEntity(Hibernate - 数据层)

它具有以下不属于数据库的属性)

  • 年龄,根据出生日期计算(假设这是一个复杂的 使用.js查看无法进行的计算
  • 映射到查找字典的状态指示符(即S - Single, M - 已婚 - D - 离婚)
  • 其他一些与业务相关的属性

当此实体推送到服务和视图层时。我应该如何构建所有这些业务逻辑?

所以要管理这个我有3种方法

  1. 将实体与@Transient一起使用并填充这些瞬态属性 使用汇编程序/工厂类

  2. 创建EntityWrapper

    public class PersonWrapper {
    
    private Person person;
    
    private int age;
    
  3. 拥有PersonModel aka POJO并填充数据库值和其他属性     在构建器类中

  4. 问题

    假设我将使用延迟加载等完整功能,存在安全问题等。 即使对于没有任何特殊属性的实体,我也将应用相同的方法(即我的DomainModelBuilder可能只是通过再次设置每个值来构建PersonModel)

    1. 以上哪种方法更好?
    2. 有更正确的方法吗?
    3. 我的方法的架构术语是什么?即域驱动程序设计等
    4. 任何其他方式来处理我的问题?
    5. 我个人更喜欢第三种方法,因为它更干净,但是一些开发人员抱怨他们正在做额外的工作和额外的处理,因为他们正在重建与实体完全一样的模型。 这就是为什么我认为2方法更好,因为他们不需要做额外的处理,仍然能够分离聚合值

1 个答案:

答案 0 :(得分:1)

网站的css / js不起作用,所以答案可能没有格式化。

我更喜欢第二种解决方案。但我使用变体,例如

public class PersonDetailViewAdapter {
    private Person person
    private PersonStatusDict personStatusDict;

    public PersonDetailViewAdapter(Person person, PersonStatusDict personStatusDict) {
         this.person = person;
         this.personStatusDict = personStatusDict;
    }

    public String getAge() {//you don't have to use a field
        return person.getAge()//age calculation seems to be a domain logic
    } 

    public String getStatusName() {
        return personStatusDict.translate(person.getStatus());
    }
}

但这实际上取决于视图的复杂程度。如果视图需求是直接的,那么编写这样一个包装器是很无聊的,那么你最终会得到一个包装器,每个方法只是委托给包装的实体(假设,状态字典很简单,不需要外部依赖,如从datasource查找,然后Person.getStatusName()更方便)。在这种情况下,我会选择第一个解决方案。

以下是我的视图适配器的示例:

public class AirTicketDetailViewAdapter {

private AirTicket ticket;

public AirTicketDetailViewAdapter(AirTicket ticket) {
    this.ticket = ticket;
}

public String getId() {
    return ticket.getId();//avoid train wreck code
}

public String getNumber() {
            //avoid dispaly null, this attribute is empty if not ticketed
    return ObjectUtils.nullSafeTrim(ticket.getNumber());
}

public String getRemark() {
    return ObjectUtils.nullSafeTrim(ticket.getRemark());
}

public String getTraveler() {
    AirTraveler traveler = ticket.getTraveler();
    String fullName = traveler.passengerType().name() + Constants.SPACE
            + traveler.fullName();
    if (traveler.isInfant()) {
        fullName += Constants.SPACE + "(" + traveler.getCarriedBy() + ")";
    }
    return fullName;
}

public String getDocumentNumber() {
    AirTraveler traveler = ticket.getTraveler();
    if (traveler.isAdult()) {
        return traveler.getDocument().getNumber();
    } else if (traveler.isChild() || traveler.isInfant()) {
        return DateUtils.format(traveler.getDocument().getDateOfBirth(),
                Constants.DEFAULT_DATE_PATTERN);
    } else {
        return PassengerType.UNKNOWN.name();
    }
}

public String getDocumentType() {
    return ticket.getTraveler().getDocument().type().name();
}

public String getStatusName() {
    return ticket.status().name();
}

public String getTotalAmount() {
    Money totalAmount = ticket.getTotalAmount();
    return totalAmount.getCurrencySymbol() + totalAmount.getAmount();
}

}

上面的View Adapter旨在

  1. 避免在jsp上使用火车残骸代码(在jsp上难以重构,当时模型不稳定)

  2. 避免在jsp上使用if / else jsplet代码。我们针对api层开发了验收测试(使测试直接与ui交互成本更高),因此任何自动化测试都没有涵盖jsps。但是,我们为这些ViewAdapter编写了单元测试(更便宜)。

  3. 我听过的缺点是

    1. 如果有人在将实体传递给ViewAdapter之后更新实体,那么第二种解决方案会引入微妙的不一致(这是一个.net相关的帖子,我现在找不到它)

    2. 如果您希望在采用委托getAge()策略时访问延迟加载属性,则可能存在会话关闭异常。这取决于你的持久性基础设施,当我们使用iBATIS时没关系,当我们切换到Hibernate而没有额外的分离时失败了。

    3. 最后但并非最不重要的是,应根据您的项目和您的团队做出决定。我不认为有一个通用的解决方案。

      希望这会有所帮助:)