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

时间:2017-09-06 04:13:25

标签: java java-ee cdi ddd-repositories

我已经阅读并开始意识到实体(数据对象 - 用于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
}

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.

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

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.

CarxManager@Stateless个bean,它们对给予它们的数据(匹配的CarxData)执行操作。他们自己进一步使用注入许多其他bean,它们都是AbstractCarManager的子类。有O(100)车型和匹配经理。

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

我以前的相关问题:How to serialize an injected bean?How can I tell the CDI container to "activate" a bean?

4 个答案:

答案 0 :(得分: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);

    }
}

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

@Repository
public interface CarRepository extends EntityRepository<Car, UUID> {}

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

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

道歉,因为我毫不怀疑你知道其中很多,但为了清楚起见,我会假设知识有限。

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

模型驱动开发

在问题中给出的模型中,我们有一个名为 State 的东西,其中包含 AbstractPlanes AbstractCars 。您正在使用JPA来保留 State ,它实际上是您的飞机和汽车的集合体。首先在软件中调用任何 State 是一种难闻的气味,因为几乎所有东西都有某种状态,但是调用我们这里所拥有的 State 的集合甚至更少感。

一个与另一个有何不同?一辆汽车是一个的一部分,另一部分是不同的,还是所有飞机和汽车都属于 State的单一实例。在这种情况下,飞机和汽车之间的关系是什么?飞机列表和汽车列表与单个 State 实体有何关系?

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

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

CarData 这样的术语也有问题,出于同样的原因,创建一个Car实体,然后一个单独的对象来存储它的数据是混乱和混乱的。

未能获得正确的模型会导致最好混淆的软件,最糟糕的是完全无法使用。这是IT项目失败的最大原因之一,项目越大,这一点就越难以实现。

修订模型

因此,从模型中我了解到我们有 Cars 并且我们有 Planes ,其实例都是具有自己身份的唯一实体。在我看来,它们是分开的东西,所以没有必要坚持将它们包含在一些聚合实体中。

public class Plane {...}

public class Car {...}

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

public class Plane implements Serializable {

    @Id
    private String name;
    private String model;
    private List<Engine> engines;

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

public enum Engine {
    PROPELLER, JET
}

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

Car实体也是如此。

@Entity
public class Car implements Serializable{

    @Id
    private String registration;
    private String type;
    private int year;

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

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

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

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

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

void operate(int i) {..}

通过以这种方式构建代码,您可以将模型(实体和值对象)与其操作的服务(存储库)的解耦方式分离,如问题中所述:

  

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

答案 1 :(得分:2)

可能是删除属性,因此序列化程序不会拾取它。这可以通过编程方式实现。

private Car2Manager getCar2Manager() {
  CDI.current().select(Car2Manager.class).get();
}

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

也可能使用JPA的@Transient

@Inject
@Transient
Car2Manager manager;

我没有对此进行测试,因此可能无效。

答案 2 :(得分:0)

什么是切入点? 这是一个Web应用程序,休息服务,肥皂服务或事件调度程序吗?

注入框架几乎总是将数据和服务分开。数据始终是POJO,完全不包含业务逻辑。在这里,假设这是一个休息服务,我将执行以下操作:

public class SSOApplication {

    public class State implements Serializable {

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

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

        // other objects similar to AbstractPlane as shown below
    }

    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
    }

    public abstract class AbstractCar implements Serializable {

        long serialNumber;

        abstract CarData getData();

    }

    public static class CarData {

        String type;
        int year;
    }

    public class Car2Data extends CarData {

        char property2;

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

    public static class Car1Data extends CarData {

        int property1;

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

    public static class Car1 extends AbstractCar {

        @Override
        CarData getData() {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }

    }

    public static class Car2 extends AbstractCar {

        @Override
        CarData getData() {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }

    }

    public static interface CarManager<T extends CarData> {

        void operate(T car, int index);

        default boolean canHandle(T carData) {
            final TypeToken<T> token = new TypeToken<T>(getClass()) {
            };

            return token.getType() == carData.getClass();
        }
    }

    @ApplicationScoped
    public static class Car1Manager implements CarManager<Car1Data> {

        public void operate(Car1Data car, int index) {
        }
    }

    @ApplicationScoped
    public static class Car2Manager implements CarManager<Car2Data> {

        public void operate(Car2Data car, int index) {
        }
    }

    @ApplicationScoped
    public static class CarService {

        @Any
        @Inject
        private Instance<CarManager<?>> carManagers;

        public void operate(int index, AbstractCar car) {
            final CarData carData = car.getData();
            final CarManager<?> carManager = carManagers.stream()
                    .filter((mng) -> mng.canHandle(carData))
                    .findFirst()
                    .orElse(IllegalArgumentException::new);

            carManager.operate(carData, index);
        }
    }
}

答案 3 :(得分:0)

如果你可以改变你的流程,也许你可以这样做:

class Car1InnerService {

    @Inject
    Car1Manager manager;

    void operate(int i, Car1 car) { 
     if (i < 0)
        return manager.operate(car.getData());
     else if (i > 1)
        return manager.operate(car.getData(), i);
     }
   }
}

我介绍了一些内部服务,它将在Car1上运行并使用Car1Manager。您的AbstractCar类当然也会丢失它的操作方法,因为从现在起您的服务将处理它。所以现在不必打电话给 car1.operate(i),你必须通过服务这样打电话:

public class SampleCar1ServiceUsage{
    @Inject
    Car1InnerService car1InnerService;

    public void carManipulator(List<Car1> carlist){
        int i = 0; //I don't know why you need this param therefore i just increment it 
        for(Car1 car: carlist){
           car1InnerService.operate(i, car);
           i++;
        }
    }   
}

当然你应该为每个其他AbsractCar孩子引入类似的功能(如果需要甚至可以提取一些抽象,例如AbsractCarInnerService定义操作方法或某些界面如果你不这样做也会这样做不想要任何其他固体方法)。然而,这个答案仍然与@Justin Cooke的答案有某种关系,在我看来你绝对应该检查他在帖子中提到的那些模式。