도메인 이벤트와 유류 소프트웨어를 함께 사용

에서

, BonitaSoft R&D 엔지니어
성숙하고 잘 작동하는 레거시 소프트웨어를 사용하지만 방대하고 복잡한 코드 라이브러리가 있고 복잡성을 증가시키지 않고 새로운 기능을 추가하고자 한다면 도메인 드라이브 설계, 깨끗한 아키텍처 또는 CQRS 기술을 통해 도움을 드릴 수 있는 방법이 있습니다.
본고는 하나의 실제 예에 근거하여 귀하가 남긴 소프트웨어에 이 기능을 추가하는 방법에 대한 단계별 지침을 제공합니다.

문맥


우리가 사용하는 예에서, 우리는 업무 절차를 집행하는 소프트웨어부터 시작한다.목표는 이러한 업무 절차에 분석을 추가하는 것이다.
이 소프트웨어는 주로 생성-읽기-업데이트-삭제(CRUD) 작업을 통해 업무 논리를 처리한다.목표는 이러한 조작에서 역 이벤트, 즉 업무 단계에서 시스템에서 발생하는 이벤트를 생성하는 것이다.
우리는 소프트웨어를 재구성할 수 있지만, 이것은 대가가 있는 것이다.반대로, 게임 이벤트를 신속하게 시작하기 위해서, 우리는 기존 소프트웨어에서 확장자를 개발하여 그것들을 생성하기로 선택했다.
이 소프트웨어 자체는 Spring과 Hibernate를 기반으로 한 고전적인 자바 응용 프로그램으로 본고의 모든 코드 예는 Kotlin에 있습니다.

방법론


다음은 우리가 따르는 절차다.
  • 디자인 분야 이벤트
  • 현재 기술 사고 가능성 평가
  • 도메인 이벤트 생성 (91678) 457
  • 릴리즈 도메인 이벤트
  • 도메인 이벤트 설계


    도메인 이벤트를 설계하기 위해서는 먼저 이 이벤트와 관련된 업무 수요를 알아야 한다. 누가 그것을 사용할 것인지, 어떻게 사용할 것인지.
    그러나 주의해야 할 것은 이러한 지역 사건은 업무에서 발생하고 있는 일을 대표하는 것이지 현재의 단일한 업무 수요를 해결하는 것이 아니다.
    우리의 예에서 우리는 업무 절차에 대해 분석을 진행하기를 희망한다.즉, 비즈니스 프로세스가 변경될 때마다 다음과 같은 이벤트를 원합니다.
  • 프로세스의 새로운 사례 시작 시
  • 끝날 때
  • 프로세스를 제출할 때
  • 우리는 이러한 활동을 설계하는 가장 좋은 방식이 녹지 개발 프로젝트로 설계하는 것이라고 발견했다.
    목표는 가능한 한 업무 논리에 가까운 역 이벤트를 만드는 것입니다.
    예를 들어 "프로세스의 새로운 사례 시작"에 대한 도메인 이벤트의 경우 이벤트를 "사례 시작"으로 명명하여 해당 사례를 시작한 사용자, 사용자가 이 사례를 시작하는 데이터와 이 사례와 관련된 프로세스에 대한 정보를 포함합니다.
    우리는 당신이 읽기 분야의 구동 디자인 기술을 강력히 건의합니다.이러한 활동을 어떻게 설계하고 업무를 어떻게 이해하는지에 관한 많은 글들이 있는데, 우리는 본고의 말미에 참고 자료를 열거했다.
    그 밖에 이 단계는 업무 절차를 이해하는 이해관계자와 함께 완성해야 한다.

    현재 기술 이벤트의 가능성 평가


    도메인 이벤트의 첫 번째 버전을 정의한 후, 남겨진 소프트웨어에서 현재 사용할 수 있는 내용으로 만들 수 있는지 확인하십시오.
    두 번째 단계로 현재의 코드 라이브러리에 깊이 들어가 현재 기술 이벤트에서 추출할 수 있는 모든 데이터를 열거하고 이 데이터는 이미 생성되었다.이 데이터는 다음과 같습니다.
  • 소프트웨어
  • 에서 구현된 탐지기
  • 프레임워크의 탐지기(대부분의 프레임워크는 탐지 변경, 탐색 변경 및 어떤 정보를 수집할 수 있는지 검증하는 방법을 제공하기 때문이다)
  • 시스템의 탐지기
  • 기타 모든 데이터의 조합간단한 예를 들면, 만약 당신이 어떤 일의 시작 상태가 있고, 게다가 현재 날짜가 있다면, 당신은 이 사건의 지속 시간이 있을 것이다.
  • 다음 그림은 우리 소프트웨어의 간소화 구조를 보여 준다.
    예를 들면 다음과 같은 몇 가지 확장점을 사용할 수 있습니다.
  • 트랜잭션 동기화
  • 이벤트 처리 프로그램
  • 휴면 사건 탐지기
  • 다음 절에서 우리는 이 자원을 어떻게 이용하여 우리의 수요를 만족시킬 것인가를 진일보 설명할 것이다.

    이해관계자와 협력하여 설계를 조정하다


    이벤트의 원래 설계는 현재 시스템 구조에 적합하지 않을 수 있습니다.이런 상황에서 당신은 몇 가지 질문을 해야 할 수도 있습니다.
  • 첫 번째 교체에서 이벤트를 위해 더 작은 기능 범위를 찾을 수 있습니까?
  • 사후 처리를 통해 원래 설계를 보존할 수 있습니까?
  • 모든 기능을 수행하기 위해 소프트웨어에서 변경해야 하는 사항을 확인할 수 있습니까?
  • 도메인 이벤트의 첫 번째 버전과 소프트웨어와 이벤트의 압축 개선이 있을 수 있습니다.
    예를 들어, 우리의 사례에서, 우리는 비즈니스 데이터라고 불리는 업데이트를 사례의 시작에 연결할 수 없다.우리는 이 두 사건을 간단하게 각각 기록하고, 미래의 교체에 후처리를 추가해서 조합하기로 결정했다.

    이벤트가 몇 가지 중요한 규칙을 따르는지 검사하다


    기억해야 할 관건은 사건은 흔히 볼 수 있는 함정을 피하기 위해 규칙을 따라야 한다는 것이다.이벤트는 다음과 같습니다.
  • 변경되지 않음
  • 유일: 유일한 식별자를 생성해야 합니다.
  • 과거로 설정: 모든 이벤트는 과거식으로 명명해야 한다
  • 타임 스탬프 있음
  • 이벤트 범위를 결정하는 데 도움이 되는 컨텍스트
  • 이 실현 분야 구동 디자인의 책[1]은 사건이 따라야 할 규칙을 상세하게 소개했다.

    도메인 이벤트 생성 방법


    설계 과정을 거친 후에 우리는 최종적으로 다음과 같은 사건 유형을 얻었다.
    CASE_STARTED, CASE_COMPLETED, TASK_STARTED, TASK_ASSIGNED, TASK_UNASSIGNED, TASK_SUSPENDED, TASK_EXECUTED, TASK_COMPLETED, CONNECTOR_STARTED, CONNECTOR_COMPLETED, BDM_INSERTED, BDM_UPDATED
    
    다음 활동 구조도 포함됩니다.
    {
     "id": "6c3b3501-f454-4eb3-be55-63176f47e767",
     "tenantId": 1,
     "name": "CASE_STARTED",
     "bpmObjectType": "CASE",
     "bpmObjectName": "LoanRequest",
     "caseExecutorId": 1,
     "contractData": {
       "amount": 100000,
       "type": "house"
     },
     "businessData": {
       "loanData": {
         "ids": [
           84
         ],
         "dataClassName": "com.company.loan.Loan"
       }
     },
     "timestamp": 1582712293203,
     "context": {
       "processId": 7564421046497327000,
       "caseId": 1052,
       "rootCaseId": 1052,
       "executorId": 1
     }
    }
    
    
    일단 우리가 역 이벤트를 정확하게 정의하면, 우리는 그것들을 만들기 시작할 수 있다.우리는 우리가 그곳에서 일하는 더 많은 기술 방면을 묘사할 것이다.

    우리가 사용할 수 있는 데이터 원본


    우리의 예에서 우리는 두 가지 데이터 원본을 사용할 수 있다.하나는 자체 제작된 감청기로 우리의 대부분의 데이터를 CRUD 조작한다.이 탐지기들은 맞춤형 기술 사건을 생성한다.
    또 다른 데이터 원본은 Hibernate 실체 관리자입니다. org.hibernate.event.spi.PostUpdateEventListenerorg.hibernate.event.spi.PostInsertEventListener이러한 탐지기는 Hibernate 세션 팩토리에 등록되어 있으며 엔티티를 업데이트하거나 삽입할 때 호출됩니다.
    다음은 이러한 프로세서의 등록 예입니다.
    val registry = sessionFactory.serviceRegistry.getService(EventListenerRegistry::class.java)
    registry.appendListeners(EventType.POST_INSERT, bdmHibernateInsertListener)
    registry.appendListeners(EventType.POST_UPDATE, bdmHibernateUpdateListener)
    
    
    Hibernate 탐지기에 대한 자세한 내용은 https://vladmihalcea.com/hibernate-event-listeners/

    기술 이벤트 처리


    Bonita Event Handler에서 캡처한 자체 제작 이벤트와 Hibernate 이벤트(Post Update Event Listeners에서 캡처한 이벤트)의 정보를 도메인 이벤트에 통합하기를 원합니다.
    다음 모드에서는 프로세스를 요약합니다.

    우리는 사무적인 환경에서 일한다.이것은 모든 작업이 업무가 끝날 때만 적용된다는 것을 의미한다.우리가 선택한 것은 등록javax.transaction.Synchronization을 하고 업무 수행 기간에 모든 기술 이벤트를 수집하여 업무 제출 시 발표하는 것입니다.
    트랜잭션 관리자에 트랜잭션 동기화를 등록하는 방법은 다음과 같습니다.
    private val bonitaEventSynchronizations: ThreadLocal<BonitaEventSynchronization> = ThreadLocal()
    
    
       private fun getSynchro(): BonitaEventSynchronization {
           return bonitaEventSynchronizations.getOrSet {
               val bonitaEventSynchronization = BonitaEventSynchronization(bonitaEventSynchronizations, bonitaEventProcessor, domainEventPublisher)
               txManager.getTransaction().registerSynchronization(bonitaEventSynchronization);
           }
       }
    
    
    업무가 끝났을 때, 우리는 이 기술 사건들을 한 개 이상의 지역 사건으로 통합할 것이다.
    여기의 목표는 기술 활동BonitaEventTypes)에서 충분한 정보를 수집하여 부족한 정보를 주동적으로 검색하지 않도록 하는 것이다.이것은 이러한 새로운 활동을 만드는 것이 성능에 미치는 영향을 최소화할 것이다.
    우리는 한 그룹DomainEventProcessor,이 있는데, 각 그룹은 하나의 유형의 역 이벤트를 생성한다.
    interface DomainEventProcessor {
       fun createDomainEvent(events: MutableList<SEvent>): List<DomainEvent>
    }
    
    예를 들어, 다음 도메인 이벤트 프로세서가 도메인 이벤트TASK_STARTED를 게시합니다.
    class TaskStartedProcessor : DomainEventProcessor {
    
       companion object {
           val eventTypes = setOf(
                   ACTIVITYINSTANCE_CREATED.name,
                   EVENT_INSTANCE_CREATED.name,
                   GATEWAYINSTANCE_CREATED.name)
       }
    
       override fun createDomainEvent(events: MutableList<SEvent>): List<DomainEvent> {
           return events.stream().filter { eventTypes.contains(it.type) }.map {
               val activity = it.`object` as SFlowNodeInstance
               val taskStartedEvent = TaskDomainEvent(UUID.randomUUID(), activity.tenantId, TASK_STARTED, activity.toBPMObjectType(), activity.name)
               taskStartedEvent.timestamp = activity.lastUpdateDate
               taskStartedEvent.context = activity.toContext()
               return@map taskStartedEvent;
           }.toList()
       }
    }
    
    

    도메인 이벤트 게시 방법



    모든 도메인 이벤트를 만든 후 도메인 이벤트 게시자DomainEventPublisher)에게 보냅니다.게시자는 다른 소프트웨어의 실행에 미치는 영향을 최소화하기 위해 자신의 라인을 사용해야 한다.
    private val executor: ExecutorService = Executors.newSingleThreadExecutor(CustomizableThreadFactory("pulsar-events"))
    
    
    매번 업무가 끝날 때마다 다음과 같은 방식으로 이 사건들을 전송한다.
    executor.submit {
       producer?.send(domainEvent)
    }
    
    예를 들어, 에이전트인 Apache Pulsar에 이러한 이벤트를 게시합니다.트랜잭션을 제출할 때 Pulsar 클라이언트는 도메인 이벤트를 JSON 형식으로 시리얼화하여 Pulsar 에이전트에 보냅니다.
    마지막으로, Pulsar에 연결기를 설정함으로써, 우리는 언제 어디서나 발표할 수 있다.우리의 예에서, 우리는 탄력적인 검색 서버를 발표했다. (참조 https://pulsar.apache.org/docs/en/io-elasticsearch/

    결론


    본고를 통해 우리는 기존 소프트웨어에서 도메인 이벤트를 발표하는 방법을 보여 주었고 이를 너무 많이 수정할 필요가 없었다.우리의 방법은 네가 실험을 시작하는 기본적인 지침이 될 수 있다.이것이 바로 우리가 업무 프로세스 소프트웨어를 사용하여 한 일이다.
    우리가 취한 방법은 우리의 업무 수요에 기술적 해결 방안을 제공했다. 즉, 역 이벤트를 조회하기 쉬운 데이터 저장소에 발표하여 데이터 분석을 할 수 있도록 한다.
    이런 방법은 신속하게 실현하기 쉽기 때문에 소프트웨어 핵심에서 직접 역 이벤트를 실현하기 전에 역 이벤트를 시도하고 그들이 잘 설계되었는지 검증할 수 있는 좋은 기회이기도 하다.
    낮은 입문 원가로 가치 창출에 새로운 가능성을 제공하고 전통 소프트웨어의 결합을 줄였다.우리가 지금 해야 할 일은 이 활동들을 구독하는 것이다.
    우리의 예에서 우리는 이러한 사건에서 실행process analytics을 시도하여 최초의 업무 수요를 만족시킬 수 있다.
    도구책
  • [1] 구현 영역 구동 디자인: ISBN-13:978-0321834577
  • [2] 영역 구동 설계: 소프트웨어 핵심의 복잡성 해결:\
    ISBN-13:978-0321125217
  • [3] https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation
  • [4] https://www.martinfowler.com/eaaDev/DomainEvent.html
  • [5] https://vladmihalcea.com/hibernate-event-listeners/
  • 좋은 웹페이지 즐겨찾기