0

我的 Web 应用程序有 3 层设计

数据层 -> 服务层 -> 表示

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

问题/困境陈述

假设我有 PersonEntity(Hibernate - Data Layer)

它具有以下并非来自数据库的属性)

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

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

所以为了解决这个问题,我有 3 种方法

  1. 使用带有@Transient 的实体并使用汇编程序/工厂类填充这些瞬态属性

  2. 创建一个实体包装器

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

问题

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

  1. 以上哪个是更好的方法?
  2. 有没有更正确的方法?
  3. 我的方法的架构术语是什么?即域驱动设计等
  4. 还有其他方法可以解决我的问题吗?

我个人更喜欢第三种方法,因为它更干净,但一些开发人员抱怨他们正在做额外的工作和额外的处理,因为他们正在重建与实体完全相同的模型。这就是为什么我认为 2 方法更好,因为它们不必进行额外的处理并且仍然能够分离聚合值

4

1 回答 1

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());
    }
}

但这实际上取决于您的观点的复杂性。如果视图要求是直截了当的,那么编写这样一个包装器是很无聊的,那么你最终会得到一个包装器,每个方法都只是委托给被包装的实体(假设,状态字典非常简单,不需要外部依赖,例如从数据源查找,则 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();
}

}

上面的视图适配器旨在

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

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

我听说过的缺点是

  1. 如果有人在将实体传递给 ViewAdapter 后对其进行更新,则第二种解决方案会引入细微的不一致(这是与 .net 相关的帖子,我现在找不到它)

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

最后但并非最不重要的一点是,应根据您的项目和团队做出决定。我认为没有一种万能的解决方案。

希望这可以帮助 :)

于 2013-08-15T01:44:09.347 回答