如何改变设计以便实体不使用注射?

Mar*_*ark 8 java ddd-repositories java-ee cdi

我已经阅读并开始意识到实体(数据对象 - 用于JPA或序列化)注入其中是一个坏主意.这是我目前的设计(所有适当的字段都有getter和setter,serialVersionUID为简洁起见我放弃了).

这是父对象,它是实体组合图的头部.这是我序列化的对象.

public class State implements Serializable {

    List<AbstractCar> cars = new ArrayList<>();

    List<AbstractPlane> planes = new ArrayList<>();

   // other objects similar to AbstractPlane as shown below
}
Run Code Online (Sandbox Code Playgroud)

AbstractPlane 它的子类只是没有注入的简单类:

public abstract class AbstractPlane implements Serializable {
    long serialNumber;
}

public class PropellorPlane extends AbstractPlane {
    int propellors;
}

public class EnginePlane extends AbstractPlane {
    List<Engine> engines = new ArrayList<>(); // Engine is another pojo
}

// etc.
Run Code Online (Sandbox Code Playgroud)

相比之下,每种具体类型的汽车都需要一个拥有某些行为的经理以及一些特定形式的数据:

public abstract class AbstractCar implements Serializable {
    long serialNumber;

    abstract CarData getData();

    abstract void operate(int condition);

    abstract class CarData {
        String type;
        int year;
    }
}

public class Car1 extends AbstractCar {

    @Inject
    Car1Manager manager;

    Car1Data data = new Car1Data(); // (getter exists per superclass requirement)

    void operate(int i) { // logic looks weird but makes the example
        if (i < 0)
            return manager.operate(data);
        else if (i > 1)
            return manager.operate(data, i);
    }

    class Car1Data extends CarData {
        int property1;

        {
            type = "car1";
            year = 1;
        }
    }
}

public class Car2 extends AbstractCar {

    @Inject
    Car2Manager manager;

    Car2Data data = new Car2Data();

    void operate(int i) {
        if (i < 31)
            return manager.operate(data);
    }

    class Car2Data extends CarData {
        char property2;

        {
            type = "car2";
            year = 12;
        }
    }
}

// etc.
Run Code Online (Sandbox Code Playgroud)

的CarxManager是@Stateless,其上的数据(匹配执行操作豆CarxData给它们).他们自己进一步使用注射许多其他豆类,它们都是AbstractCarManager.有O(100)车型和匹配经理.

序列化时的问题State是序列化抽象汽车列表与子类中的注入不相符.我正在寻找一种将注射与数据保存过程分离的设计.

我以前的相关问题:如何序列化注入的bean?和我怎么能告诉CDI容器"激活"豆?

Jus*_*oke 8

您可以使用存储库模式.将业务逻辑放入服务中,并将存储库(将抽象机制抽象化)和管理器注入其中.存储库隐藏了业务服务中的持久性实现细节,而实体只是简单的POJO.

它看起来像下面的Foo是实体Bar的id:

public class CarService {

    @Inject
    CarRepository carRepository;

    @Inject
    CarManager manager;

    piblic void operate(final Foo foo) {

        Bar myBar = carRepository.retrieve(foo);
        manager.doSomethingTo(myBar);
        carRepository.persist(myBar);

    }
}
Run Code Online (Sandbox Code Playgroud)

另请参阅:存储库模式逐步说明,http://deviq.com/repository-pattern/.一些框架(如Spring Data JPA或deltaspike)已经为您实现了存储库模式,您需要做的就是提供如下所示的接口,并在后台生成实现:

@Repository
public interface CarRepository extends EntityRepository<Car, UUID> {}
Run Code Online (Sandbox Code Playgroud)

标记回答您的更多细节请求我将提供一个改进的解决方案,因为问题中的示例对我来说真的没有意义,并展示了一些导致有问题的软件的反模式.

找到问题的一个很好的解决方案涉及很多不同的考虑因素,其中很多都是非常大的主题,有许多关于它们的书籍,但我会尽力说明我的想法,以解决上述问题.

并且道歉,因为我毫不怀疑你知道其中许多,但为了清楚起见,我将假设有限的知识.

解决这个问题的第一步不是关于代码,而是关于模型本身,模型驱动开发在Eric Evan的书中被广泛涵盖,如下面的评论所述.该模型应该驱动实现,并且还应该作为分层体系结构的一部分存在于其自己的层上,并且由实体,值对象和工厂组成.

模型驱动开发

在问题中给出的模型中,我们有一个叫做State的东西,它包含AbstractPlanes和AbstractCars.您正在使用JPA来保持状态,这实际上是您的飞机和汽车的总和.首先在软件中调用任何状态是一种难闻的气味,因为几乎所有东西都具有某种状态,但是调用我们在这里所拥有的东西是一种聚合状态甚至没有意义.

一个国家与另一个国家有何不同?是一个汽车零件国家和不同的另一部分国家还是所有的飞机和汽车属于一个实例的情况下国家.在这种情况下,飞机和汽车之间的关系是什么?飞机清单和汽车清单如何与单个国家实体有任何关系?

好吧,如果State实际上是一个机场,我们对当前有多少架飞机和汽车感兴趣,那么这可能是正确的型号.如果国家是一个机场,它将有一个名称或身份,如机场代码,但它没有,所以......

...在这种情况下,似乎State是一个对象,它被用作方便我们访问对象模型的对象.因此,我们通过实施考虑有效地推动我们的模型,当我们应该反过来这样做并从我们的模型推动我们的实现.

像CarData这样的术语出于同样的原因也存在问题,创建Car实体然后单独的对象来存储其数据是混乱和混乱的.

如果不能正确地获得模型,则会导致最好混淆的软件,最糟糕的是完全不起作用的软件.这是IT项目失败的最大原因之一,项目越大,这项工作就越难以实现.


修订模型

所以从模型中我了解到我们有汽车,我们有飞机,其实例都是具有自己身份的独特实体.在我看来,它们是分开的东西,因此坚持将它们包含在某个聚合实体中没有意义.

public class Plane {...}

public class Car {...}
Run Code Online (Sandbox Code Playgroud)

另一个考虑因素是在模型中使用抽象类,通常我们希望应用有利于组合而不是继承的原则,因为继承可能导致隐藏行为,并且它可能使模型难以阅读.例如,为什么我们有ProperllerPlane和EnginePlane?当然,螺旋桨只是一种发动机?我大大简化了模型:

public class Plane implements Serializable {

    @Id
    private String name;
    private String model;
    private List<Engine> engines;
Run Code Online (Sandbox Code Playgroud)

Plane是具有自己的属性和标识的实体.为了存储属性,不需要在现实世界中代表任何东西的其他类.引擎对象当前是一个枚举,表示平面中使用的引擎类型:

public enum Engine {
    PROPELLER, JET
}
Run Code Online (Sandbox Code Playgroud)

如果引擎本身需要身份,就像在现实生活中引擎序列号和事物被跟踪一样,那么我们会将其更改为对象.但是我们可能不希望允许访问它,除非通过Plane实体实例,在这种情况下Plane将被称为聚合根 - 这是一个高级主题,我会推荐Evan的书来获取有关聚合的更多详细信息.

Car实体也是如此.

@Entity
public class Car implements Serializable{

    @Id
    private String registration;
    private String type;
    private int year;
Run Code Online (Sandbox Code Playgroud)

以上是您在模型基础上提供的所需内容.然后我创建了几个工厂类来处理这些实体的实例创建:

public class CarFactory {

    public Car makePosrche(final String registrationNumber) {
        Car porsche = new Car();
        porsche.setRegistration(registrationNumber);
        porsche.setType("Posrshe");
        porsche.setYear(1986);
        return porsche;
    }
}

public class PlaneFactory {

    public Plane makeSevenFourSeven(final String name) {
        Plane sevenFourSeven = new Plane();
        List<Engine> engines = new ArrayList<Engine>();
        engines.add(JET);
        engines.add(JET);
        engines.add(JET);
        engines.add(JET);
        sevenFourSeven.setEngines(engines);
        sevenFourSeven.setName(name);
        return sevenFourSeven;
    }

    public Plane makeSpitFire(final String name) {
        Plane spitFire = new Plane();
        List<Engine> engines = new ArrayList<Engine>();
        engines.add(PROPELLER);
        spitFire.setEngines(engines);
        spitFire.setModel("Spitfire");
        spitFire.setName(name);
        return spitFire;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们在这里所做的是根据单一责任原则将问题分开,每个班级应该只做一件事.


现在我们有了一个模型,我们需要知道如何与它进行交互.在这种情况下,我们很可能如果使用JPA将汽车保持在一个名为Car和飞机的表中.我们将通过存储库,CarRepository和PlaneRespository提供对这些持久化实体的访问.

然后,您可以创建名为services的类,这些类会注入存储库(以及您需要的任何其他内容),以便对汽车和飞机的实例执行CRUD(创建读取更新删除)操作,此外,您可以将业务逻辑应用于这些.比如你的方法:

void operate(int i) {..}
Run Code Online (Sandbox Code Playgroud)

通过以这种方式构造代码,您可以将模型(实体和值对象)与它们如何持久存储(存储库)从您对其进行操作的服务(如您的问题中所述)进行分离:

我正在寻找一种将注射与数据保存过程分离的设计.


Alb*_*Bos 2

一种可能性是删除该属性,这样序列化器就不会拾取它。这可以通过以编程方式获取来实现。

private Car2Manager getCar2Manager() {
  CDI.current().select(Car2Manager.class).get();
}
Run Code Online (Sandbox Code Playgroud)

我不认为这是一个干净的解决方案,但它应该是一个可行的“解决方案”

使用 JPA 也可能有效@Transient:

@Inject
@Transient
Car2Manager manager;
Run Code Online (Sandbox Code Playgroud)

我还没有测试过这个,所以它可能不起作用。