Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Практический опыт реализации сложной предметной...

CUSTIS
April 06, 2019

Практический опыт реализации сложной предметной логики с помощью агрегатов

Выступление Вячеслава Муравлева, нашего руководителя производственной группы, на конференции JPoint (Москва, 6 апреля 2019).

CUSTIS

April 06, 2019
Tweet

More Decks by CUSTIS

Other Decks in Programming

Transcript

  1. Практический опыт реализации сложной предметной логики с помощью агрегатов Вячеслав

    Муравлев Руководитель группы разработки JPoint Москва, 6 апреля 2019
  2. Немного о себе | Руковожу группой разработки в проекте автоматизации

    образовательного процесса в вузах | В энтерпрайз-разработке с 2004 года | В основном занимаюсь бэкенд-разработкой 2 18
  3. Немного о проекте | Облачное решение, рассчитанное на много вузов

    | Различные категории пользователей | Большая и сложная предметная область | Используем domain-driven design 3 18
  4. Архитектура ФПС: Ведение каталога курсов ФПС: Планирование учебных периодов …

    Domain-level REST API Application-level REST API Frontend BE-1 BE-n BE-2 Gateway Frontend BE-1 BE-n BE-2 Gateway 4 18
  5. Типовой способ реализации предметной логики @Table (not null, foreign key)

    @Entity (get/set) @Repository (CRUD) @Service (сделай все бизнес-проверки ну и все остальное) @RestController (вызови сервисы и репозитории в транзакции и отдай JSON) Мало логики Много логики 5 18
  6. Насыщаем модель предметной логикой: выделяем агрегаты | Переносим в доменные

    объекты операции с тесно связанными объектами | Бизнес-правила и инварианты – в доменные объекты | Анализируем связи между сущностями 6 18
  7. Агрегаты в предметной области Состояние курса Технология реализации курса Образовательный

    результат курса Сотрудник Учебный курс 7 18 Инвариант В состоянии «Опубликован» должна быть указана технология реализации курса
  8. Rich API: когда и как | Используем для связанных объектов

    со сложной структурой и своим жизненным циклом | Все манипуляции со связанными объектами – только через API корневого объекта | Соблюдение инвариантов в корневом объекте 9 18
  9. Rich API: работа с вложенными сущностями 11 18 API для

    работы с вложенными сущностями Корневая сущность Часть агрегата: связанная сущность @Entity @Table(name = "course_unit_lesson") public class Lesson extends AuditableEntity { @Embedded @AssociationOverride(name = "requirementContainers", joinColumns = @JoinColumn(name = "lesson_id")) private LessonRequirementsCollection requirements = new LessonRequirementsCollection(); public LessonRequirementId createRequirement(LessonRequirement reqData) { LessonRequirementContainer reqContainer = new LessonRequirementContainer(args); requirements.add(reqContainer); reqData.initFromLesson(this); return reqContainer.getLessonRequirementId(); } public void replaceRequirement(String reqId, LessonRequirement reqData) throws LessonRequirementMissingException { requirements.replace(reqId, reqData); } public LessonCopyResult copy(String lessonId, CourseUnit targetCourseUnit, boolean copyRequirements) { // copy internal objects, check invariants… } }
  10. Value objects: когда и как | Используем для связанных объектов

    со сложной структурой и без жизненного цикла | Сериализуем value object в JSON и сохраняем в БД | Используем JPA AttributeConverter 13 18
  11. Соблюдение инвариантов с помощью событий | Используем для разрастающихся агрегатов

    с вложенными сущностями | Переносим часть API во вложенные сущности | Используем событийные механизмы для соблюдения целостности 14 18
  12. @Entity @Table(name = "planned_cohort") @EntityListeners({CourseUnitRealizationPartEntityListener.class}) public class PlannedCohort extends CourseUnitRealizationPart

    {} public class CourseUnitRealizationPartEntityListener { @PrePersist @PreUpdate @PreRemove public void incrementCourseUnitRevision(CourseUnitRealizationAware entity) { entity.courseUnitRealization().checkInvariants(); } } Соблюдение инвариантов с помощью Hibernate 15 18 Корневая сущность агрегата: план проведения курса Проверяем инварианты Hibernate listener Часть агрегата: поток обучения @Entity @Table(name = "course_unit_realization") @EntityListeners({CourseUnitRealizationPartEntityListener.class}) public class CourseUnitRealization { @OneToMany(fetch = FetchType.LAZY, mappedBy = "courseUnitRealization", cascade = CascadeType.ALL, orphanRemoval = true) private Set<PlannedCohort> cohorts = new HashSet<>(0); @OneToMany(fetch = FetchType.LAZY, mappedBy = "courseUnitRealization") private Set<CourseUnitPlanElement> planElements = new HashSet<>(0); }
  13. Как мы делаем агрегаты | Делаем насыщенный API для корневых

    объектов | Храним value objects целиком в БД | Соблюдаем инварианты с помощью событийных механизмов 16 18
  14. Рекомендую почитать | Эрик Эванс «Предметно-ориентированное проектирование (DDD): структуризация сложных

    программных систем» | Вон Вернон «Реализация методов предметно- ориентированного проектирования» 17 18
  15. Value object: делаем сам объект @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =

    JsonTypeInfo.As.PROPERTY, property = "@type") @JsonSubTypes({ @JsonSubTypes.Type(value = LessonClassroomRequirement.class, name = LessonRequirementType.TYPE_CLASSROOM), @JsonSubTypes.Type(value = LessonProvisionRequirement.class, name = LessonRequirementType.TYPE_LESSON_PROVISION), @JsonSubTypes.Type(value = LessonTeacherRoleRequirement.class, name = LessonRequirementType.TYPE_TEACHER_ROLE), @JsonSubTypes.Type(value = LessonSchedulingIntervalFromPrevLessonRequirement.class, name = LessonRequirementType.TYPE_SCHEDULING_INTERVAL_FROM_PREV_LESSON), @JsonSubTypes.Type(value = LessonTimeRequirement.class, name = LessonRequirementType.TYPE_TIME), @JsonSubTypes.Type(value = LessonPreparationRequirement.class, name = LessonRequirementType.TYPE_PREP), }) public abstract class LessonRequirement implements Serializable { public void initFromLesson(Lesson lesson) { // для установки данных из родительской УВ } public abstract LessonRequirement copy(); }
  16. Value object: используем в сущности @Entity @Table(name = "lesson_requirement") public

    class LessonRequirementContainer extends AuditableEntity implements AuthorizationBatchEntity { // все прочее @Convert(converter = LessonRequirementJsonConverter.class) private LessonRequirement requirement; } // JSON конвертер public class LessonRequirementJsonConverter implements AttributeConverter<LessonRequirement, String> { @Override public String convertToDatabaseColumn(@Valid LessonRequirement requirement) { // implement using Jackson } @Override @Valid public LessonRequirement convertToEntityAttribute(String databaseDataAsJSONString) { // implement using Jackson }