`
yangdong
  • 浏览: 66272 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

如何保证 Entity 的状态

阅读更多
DDD 的经典 Cargo 1.1 示例中,domain.experiment 包下的 ValueObject 接口定义了一个 copy() 方法。我认为并无用处。有的资料说防止外部修改。这个在 Java 下说不通。Java 没有指针的概念,所以不会有 C/C++ 下的指向指针的指针。也就是说,把 ValueObject 的引用暴露出去,客户代码无法修改这个引用指向的地址。又因为 ValueObject 是不变体,所以客户代码也无法修改引用指向的对象的值。所以暴露 ValueObject 的引用是安全的。

有的人会说 ValueObject 可能不一定是不变体,比如 Cargo 示例中的 Leg,它就包含对一个 Entity 的引用。我认为这个有改进的余地。如果 ValueObject 要包含一个 Entity,它应该包含这个 Entity 的拷贝。不然,ValueObject 从语义上说就不叫“值对象”了,它变得复杂了。而状态可变对象的滥用是万恶之源。

现在假设我们要对 Leg 中引用的 Entity 做一个拷贝,我们需要建一个 EntityDTO 类,这个类的每个域都跟 Entity 一样。然后我们逐个域地进行复制。如果这个 Entity 还包括其它 Entity,那么这个过程就递归下去。你能想象这有多么可怕吗?如果我们在 Entity 的接口中加入一个契约,让它支持拷贝。这样我们就可以避免这样递归拷贝情况的出现。不仅如此,因为每个 Entity 都对自己的拷贝过程负责,所以如果未来有改动导致拷贝方法也需要修改,这么这个修改只会局限在应该对此负责的 Entity 上,而不会散布到所有使用拷贝的地方。

我们有时候需要对 Entity 的 ID 以外的字段做拷贝。就好像两个人,有不同的 ID。我想拷贝那个特别优秀人物的所有特征,只不过,我还想做我自己。这就要求应该提供两个做 Entity 拷贝的接口:
1. copy(Identity id) // Identity 是该 Entity 所对应的 ID 类型
2. snapshot() // 相当于 copy(this.identity())

这样会带来一个问题:Entity 的实现变复杂了。粗看是这样,但我们要问一个问题:这个复杂性是谁带来的,谁应该对此负责?在看了 Clojure 的作者 Rich Hickey 在 InfoQ 上的演讲后我发现,这是由于以前 Entity 没有对时间作管理(Time Management)。我们不能要求正在赛跑的运动员说,你们都定格一下,让我把这个瞬间留住。这种要求做快照的需求有时是非常正常,而且必须得到满足的。比如电子商务平台的交易快照。交易在变过状态后必须留有快照以便日后出现纠纷时有证可查。

也许你会说,不是所有 Entity 都是交易记录这样敏感的。没错,我们再看一个例子。我前段时间写过一个 Hosts 绑定工具,有一个 Entity:Hosts。它是 hosts 文件的逻辑内容的抽象。还有一个 aggregate 的内部 Entity:HostLine。它是对每一行的抽象(当然你可以说它可以做成 ValueObject,但我只是拿它来举例说明有多个 Entity 的 aggregate 所具有的问题)。Hosts#addHostsAsGroup(Hosts hosts) 这个方法会把参数 hosts 的内容跟自身这个实例的内容做合并。所以你很自然地会认为,hosts 自己跟自己做合并应该会产生出比原来的内容大一倍的 hosts。这正是我的测试用例的代码所做的。结果大出我的意料。在方法的开头,我校验了参数 hosts 的所有行状态。而在进行合并的过程当中这个校验/断言被打破了!一开始我以为是 JVM 的 bug,因为程序根本不是多线程的。后来才意识到是因为在合并的过程当中,行因为绑定内容重复而被注释掉,这样状态一变,后续的代码才会出现问题。在发现了 Entity#snapshot 的价值之后我重写了程序,这次在方法的开头我给参数做一个防御性拷贝,这回问题解决了。

也许你还会说这个例子还是具有特殊性。那我要说,你不知道什么时候这样需要会出现,可能因为技术上的问题(像上面的 hosts 的例子),可能因为业务需求的变化。我们需要对时间做出管理!你真正实现的时候会发现这个过程并不会带来太多负担,只是相当于写一个 ValueObject 的 equals(Object) 方法的工作量。而且 Entity 接口的 sameIdentityAs(T), equals(Object), hashCode() 方法都可以通过抽象基类来做。我最后会给出代码。

所以我得出一个结论:这个复杂性是可变体固有的,而不是因为业务的临时需求硬加的。你可以说这加重了我们的负担(实际上只有一点),但请不要说解决这个问题不是我们的责任,我就当没看见,到时候再说。

/**
 * An entity, as explained in the DDD book.
 *
 * @param <T>  the target type which should implements this {@code Entity} interface
 * @param <ID> the type of the identity field
 *
 * @author Eric Evans
 * @author ydong
 */
public interface Entity<T extends Entity<T, ID>, ID> {

    /**
     * Returns the identity of this entity.
     */
    ID identity();

    /**
     * Entities compare by identity, not by attributes.
     *
     * @param other The other entity.
     * @return true if the identities are the same, regardles of other attributes.
     */
    boolean sameIdentityAs(@Nullable T other);

    /**
     * Compares the identity equality between the given object and this instance.
     * Two entities are equal if their identities are equal.
     *
     * @param obj the object whose identity's goinng to be compared
     * @return true if they are equal, false otherwise
     */
    @Override
    boolean equals(@Nullable Object obj);

    /**
     * Returns the hash code of the identity.
     */
    @Override
    int hashCode();

    /**
     * A type-safe replacement for {@link Object#clone() clone()}. The copy
     * returned by this function uses the given argument as its identity.
     * Whether the returned copy is readonly or changable solely depends on
     * implementations.
     *
     * @param identity which is used by the copy returned as identity
     * @return a copy with the given argument as identity
     */
    T copyAs(ID identity);

    /**
     * Same as {@link #copyAs(java.lang.Object) copyAs(Object)} except that
     * the identity of the returned copy is the same with this instance.
     */
    T snapshot();

}


/**
 * A support class helps implementing {@link Entity} easier.
 *
 * @param <T>  the target type which should implements this {@code Entity} interface
 * @param <ID> the type of the identity field\
 *
 * @author ydong
 */
public abstract class EntitySupport<T extends Entity<T, ID>, ID>
        implements Entity<T, ID> {

    /**
     * Returns the class object of the entity interface/class.
     */
    protected abstract Class<T> entityInterface();

    public boolean sameIdentityAs(@Nullable T other) {
        return other != null && identity().equals(other.identity());
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(@Nullable Object obj) {
        return obj != null && entityInterface().isAssignableFrom(obj.getClass())
                && sameIdentityAs((T) obj);
    }

    @Override
    public int hashCode() {
        return identity().hashCode();
    }

    /**
     * Returns the text representation of the identity field. More formally,
     * it's returned like this: {@code return identity().toString();}.
     */
    @Override
    public String toString() {
        return identity().toString();
    }

    public T snapshot() {
        return copyAs(identity());
    }

}
分享到:
评论

相关推荐

    Entity Framework 4 In Action

    - 讨论了如何使用事务来保证数据操作的原子性、一致性、隔离性和持久性。 ##### 第三部分:精通Entity Framework - 该部分预计会涉及更高级的主题,如性能调优、高级查询模式、批量操作等。 #### 三、书籍特点与...

    EntityBeanTest

    在Java EE框架下,Entity Bean扮演着数据存储的角色,为应用提供事务管理和数据一致性保障。 在描述中提到的"EntityBeanTest"很可能是测试类或测试用例,用于验证Entity Bean的功能、性能以及与其他系统组件(如...

    EntityEngine

    - **同步与同步策略**:在多人游戏中,同步是关键,EntityEngine 可能采用状态同步、权威服务器模型或客户端预测等方法来保证游戏世界的一致性。 4. **自上而下的生存游戏代码**: - **游戏循环**:自上而下的...

    UIToEntity

    UI是用户与应用交互的图形化部分,包括按钮、输入框、列表等元素,它们负责收集用户输入并展示应用的状态。而Entity则代表应用程序中的业务对象或数据模型,它们通常存在于数据库中,用于存储和管理数据。 在传统的...

    EntityBeanOne2One

    无状态会话Bean(Stateless Session Beans),不保存任何会话状态。 6. **事务管理**: - 在Java EE环境中,事务管理通常是自动的,但开发者需要理解何时开始和结束事务,特别是在处理一对一关系时,以保证数据...

    EDA技术-如何消除状态机毛刺[归纳].pdf

    【EDA技术-如何消除状态机毛刺】 在电子设计自动化(EDA)领域,有限状态机(FSM)是实现数字系统控制逻辑的关键组件。...在实际设计中,应结合具体应用场景选择合适的方法,以保证设计的性能和质量。

    A_survey_of_named_entity_recognition_and_classification.pdf

    命名实体识别和分类(Named Entity Recognition and Classification,简称NERC)是自然语言处理(Natural Language Processing,简称NLP)中的一个基础任务,主要针对文本中的命名实体进行识别和分类,如人名、组织名、...

    按键去抖设计基于VHDL描述状态机的方法.doc

    按键去抖设计是一种常见的电子技术处理手段,特别是在使用机械式轻触按键的系统中,为了确保输入的稳定性。本设计基于VHDL描述状态机...通过VHDL编程,实现了对按键信号的高效处理,为实际应用中的按键输入提供了保障。

    EJB2.0 Entity bean(PDF) .zip_EJB2 enti_EJB2.0 P_ejb_ejb2 CMP sup

    - **实体Bean接口**:包含无状态的业务方法,供客户端调用。 - **实体Bean实现类**:实现了接口中的业务逻辑,并包含了持久化字段。 - **实体Bean的Home接口**:提供了创建、查找和删除实体Bean实例的方法。 - **...

    订单状态

    使用Entity Framework,我们可以将Order类映射到数据库表,而OrderStatus枚举则可以映射到数据库的枚举类型或者整型字段,这样可以方便地在数据库中存储和检索订单状态。 此外,为了保证系统的健壮性,还需要考虑...

    java面试试题大全

    - 创建方法:用于生成 Bean 实例,无状态 Session Bean 有一个无参的创建方法,有状态 Session Bean 至少有一个,Entity Bean 可以有多个。 - Finder 方法:特定于 Entity Bean,根据指定条件在数据库中查找实体。...

    java 序列化 将实体类本地序列化

    但请注意,对于大量数据或长期存储,通常推荐使用专门的数据库系统,因为它们提供了更好的性能、可扩展性和数据一致性保证。 此外,序列化还支持版本控制,通过`serialVersionUID`字段可以指定对象的序列化版本。...

    110024.rar

    开发者可以定义实体类,Entity Framework会自动创建对应的数据库表,并能根据对象状态自动执行增删改查操作,极大地提高了开发效率。 SQLServer是微软公司的一款关系型数据库管理系统,以其稳定性和高性能在企业级...

    EDA课程设计报告(交通灯控制系统)

    通过EDA工具可以有效提升设计效率并保证设计质量。 - **目标**: 模拟现实世界中的**十字路口交通信号灯**工作流程,利用VHDL硬件描述语言来实现对交通信号灯控制器的设计。 #### 设计任务与要求 - **模拟流程**: ...

    EJB的基础知识资料

    因为每个实例都是独立且等价的,所以可以高效地服务于大量客户,例如在集群中,如果一个服务器失效,请求可以被重定向到其他服务器,保证了服务的高可用性。然而,如果需要保持状态,如计数器应用,无状态会话Bean...

    EJB003软件开发考试培训资料

    - Session Bean的生命周期:对于Stateless Session Bean,容器根据需求动态创建实例,不保证每个客户端有独立的实例。而Stateful Session Bean会在首次调用时创建实例,并与特定客户端绑定,后续调用都与该实例关联...

    EF(EntityFramework) 插入或更新数据报错的解决方法

    在使用Entity Framework(EF)进行数据库操作时,有时可能会遇到插入或更新数据时出现报错的情况。这通常是由多种原因引起的,例如并发控制问题、主键冲突或实体状态管理不当等。本文将针对"Store update, insert, ...

    EJB3.0入门图文教程

    - **可伸缩性和安全性**:EJB容器提供了自动的事务管理、安全控制、性能监控等功能,保证了应用的稳定性和安全性。 在《EJB3.0开发Entity.pdf》中,你将学习如何使用JPA和实体Bean来管理数据模型。《EJB3.0开发...

    Jboss下开发ejb应用之一会话bean的应用

    EJB分为三种主要类型:会话Bean(Session Beans)、实体Bean(Entity Beans)和消息驱动Bean(Message-driven Beans)。 二、会话Bean 会话Bean代表客户端的临时业务逻辑,它们不持久化数据,而是处理业务逻辑并...

Global site tag (gtag.js) - Google Analytics