2016-06-17 2 views
2

저는 최근에 DDD를 살펴보기 시작했고 오래된 개인 프로젝트를이 패턴으로 리팩토링했습니다. 나는 Evans 청서의 중간 정도에 있으며, 거기에 대한 답변을 찾을 수 없을 것입니다.DDD Repo-repo.findByChildId (id) AND repo.transferChild (to, from, child)

기본적으로 내 응용 프로그램은 재고 추적 장치입니다. 인벤토리에는 항목 모음이 포함되고 항목은 인벤토리간에 전송 가능한 항목입니다. 인벤토리에는 transferIn() transferOut()과 같은 메소드가 있습니다. 여기에는 인벤토리가 이미 가득 차 있지 않은지 또는 항목이 양도 할 수있는 상태인지 확인하는 등의 유효성 검사 로직이 포함됩니다. 이러한 제약 때문에 인벤토리가 집합 루트이고 해당 항목이 엔티티라고 믿게됩니다.

1) 사용자가 인벤토리에 대해 특정 항목 엔티티를 요청한 경우 현재 해당 항목이있는 인벤토리를 반환하는 inventoryRepo.findByItemId(id)을 갖고 싶습니다. 서비스를 통해

2)과 같이 수행합니다 :

boolean requestItemTransfer(destInvId, itemId){ 
    Inv from = invRepo.findByItemId(itemId); 
    Inv to = invRepo.findById(destInvId); 
    from.transferOut(itemId); 
    to.transferIn(from.getItem(itemId)); 
    return invRepo.transferChild(to, item); //Edited 
} 

은 기본적으로 재고 클래스 (풍부한 도메인 모델 내 검증 로직을 작성)를 다음 예외가없는 경우 나는 REPO를 사용하는 나는 할 수 있도록 .transfer() 메서드를 사용하여 변경 내용을 유지합니다.

DDD를 위반합니까? 더 나은 대안이 있습니까?

내가 읽은 것에서 이것은 틀에 박힌 경우에만 유효하다고 보았습니다. 내가 찾은 모든 예제는 1 개의 루트 인스턴스 내에 만 존재할 수있는 엔티티를 보여줍니다. 또한 은행 계좌 이체 예가 있지만 값 개체 인 금액을 처리하고 송금 저장소가 있습니다. 송금이 특정 시나리오에 기록되기 때문에 광산에는 저장되지 않기 때문입니다.

편집 : 사용 사례는 다음과 같습니다 :

1) 사용자가 자신의 재고와 그 항목의 목록을 요청합니다.

2) 사용자는 1 개의 인벤토리에서 1 개 이상의 항목을 선택하고 다른 인벤토리로 전송하도록 요청합니다. 이것은 내 TransferService가 들어 와서 지정된 재고 목록에서 txIn 및 txOut을 조정하고 repo를 통해 이러한 변경 사항을 유지합니다. 인프라 서비스가되어야할까요? 그게 분명하지 않다.

3) 사용자는 해당 항목이 현재있는 인벤토리의 목록과 관련하여 인벤토리로 전송할 수있는 항목 집합을 미리 정의합니다. TransferService는 현재 항목이있는 위치를 찾고 유스 케이스 2에서와 같이 나머지 부분을 조정합니다 .

EDIT2 : repo.transfer 정보 사실 이것은 제약 조건입니까/최적화입니까? 데이터 측면에서부터 내가 말한 것에서는 항목을 찾아보고 가리키는 인벤토리 ID를 변경합니다. 이것은 품목이 즉시 2 개의 재고 목록에있을 수 없기 때문입니다. 그러므로 repo.update(fromInvInNewState)repo.update(toInvInNewState) 대신 인벤토리의 전체 상태 (모든 항목이 이동하지 않았으며 상태의 나머지 부분은 해당 위치에있는 항목에서 파생되기 때문에 다시 작성하지 않으므로 repo.moveChild(toInv, child)이 있습니다) 포인트), 일부 항목을 주변으로 이동하십시오.

답변

1

먼저 시나리오에 정의 된 도메인 모델을 다시 정리하고자합니다.

  • 사용자는 재고가 있습니다

    당신은 당신이 다음 사양을 가진 재고 추적기를 구축하고 있다고 말했다.
  • Iventory는 항목으로 구성됩니다.
  • 사용자는 하나의 인벤토리에서 다른 인벤토리로 항목을 전송할 수 있습니다. "사용자가 자신의 재고 목록 및 해당 품목의 목록을 요청하고 사용자가 1 개의 재고에서 1 개 이상의 품목을 선택하고 다른 재고로 보내 달라는 요청을 ..."이라고 말한대로 두 재고가 사용자에게 속한 것 같습니다. 한편

, 당신이 지적 불변은 다음과 같습니다 InventoryB가 아닌 경우에만

  • 항목이 다른 재고 (InventoryB)에 이미있는 재고 (InventoryA)에서 전송 될 수있다 이미 가득 찼습니다. Item을 전송할 수없는 경우 InventoryA에 보관해야합니다.
  • 잘 이해하면 사용자가 그의 저장소간에 항목을 전송합니다. 같은

뭔가 :

class TransferItemService { 
    public function execute(TransferItemRequest request) 
    { 
     user = userRepository.findOfId(request.userId()); 
     user.transferItem(request.itemId(), request.fromInventoryId(), request.toInventoryId()); //Checks invariant -> the given Item is in one of his Inventories, the destination Inventory is owned by him, the destination Inventory is not full and finally transfers the Item 
     userRepository.save(user); 
    } 
} 

이제 내 사업이 최종 일관성 다룰 수 있는지 알 필요가 집계 루트/S를 정의하기 위해있다. 즉, 항목 이동이 원자 단위로 수행되어야하거나 (단 하나의 요청) 수행되어야하거나 일정 시간 (둘 이상의 요청)이 소요될 수 있습니다.

경우 사업 없음 최종 일관성

는 최종 일관성이 도메인이 일관성을 유지하고 불변에 정렬되도록하려면, 사용자가 그와 같은 고유 AggregateRoot 것, 여기에 허가되어 있지 않은 것을 말한다 그의 재고 목록 사이의 연결 고리입니다. 이 경우 모든 재고 목록을 항목과 함께로드하여 성능 문제가 발생할 수 있습니다. User, Inventory, Item :

당신이 궁극적 consitency로 갈 수있는 경우 최종 일관성

, 다음 집계 뿌리를 가질 수 있습니다.

class User { 
    private string id; 
    private List<UserInventory> inventories; 

    public function transferItem(itemId, fromInventoryId, toInventoryId) 
    { 
    fromUserInventory = this.inventories.get(fromInventoryId); 
    if(!fromUserInventory) throw new InventoryNotBelongToUser(fromInventoryId, this.id); 

    toUserInventory = this.inventories.get(toInventoryId); 
    if(!toUserInventory) throw new InventoryNotBelongToUser(toInventoryId, this.id); 

    toUserInventory.addItem(itemId); 
    fromUserInventory.deletetItem(itemId); 
    } 
} 

class UserInventory { 
    private String identifier; 
    private int capacity; 

    public function deleteItem(userId, itemId) 
    { 
     this.capacity--; 
     DomainEventPublisher.publish(new ItemWasDeleted(this.identifier, itemId)); 

    } 

    public function addItem(userId, itemId) 
    { 
    if(this.capacity >= MAX_CAPACITY) { 
     throw new InventoryCapacityAlreadyFull(this.identifier); 
    } 
    this.capacity++; 

    DomainEventPublisher.publish(new ItemWasAdded(this.identifier, itemId)); 
    } 
} 

공지 사항 UserInventory 즉, Inventory 집계 루트되지 않습니다 : 그래서, 이전 코드를 사용하여 항목을 전송하는 유스 케이스 모델 :처럼이 경우

class TransferItemService { 
    public function execute(TransferItemRequest request) 
    { 
     user = userRepository.findOfId(request.userId()); 
     user.transferItem(request.itemId(), request.fromInventoryId(), request.toInventoryId()); //Checks invariant -> the given Item is in one of his Inventories, the destination Inventory is owned by him, the destination Inventory is not full and finally transfers the Item 
     userRepository.save(user); 
    } 
} 

을의 transferItem 방법은 보일 것이다 , 식별자 참조 및 실제 용량 인 현재의 용량이있는 VO입니다. Inventory.

지금, 당신은 asynchonously 각 재고를 업데이트하는 리스너를 가질 수 있습니다

class ItemWasRemovedListener() 
{ 
    public function handleEvent(event) 
    { 
    removeItemFromInventoryService.execute(event.inventoryId(), event.itemId()); 
    } 
} 

class ItemWasAddedListener() 
{ 
    public function handleEvent(event) 
    { 
    addItemToInventoryService.execute(event.inventoryId(), event.itemId()); 
    } 
} 

내가 만든 않는 한 나는 우리가 요청 및 우리 당 우리의 모든 불변, 우리가 수정 한 일 개 집계 루트를 만족 있다고 생각 실수 인벤토리 작업을 수행하기 위해 모든 항목을로드 할 필요는 없습니다.

잘못된 것이 있으면 제게 알려주세요. D.

+0

에반스와 버논의 책을 막 완성했습니다. 나는 당신의 답변에 동의하지만 또 다른 질문이 떠올랐다. 나는 사용자가 AR이고 인벤토리 인스턴스의 불변량을 처리하는 것에 동의합니다. 이제 문제는 일부 인벤토리가 사용자간에 공유된다는 것입니다. 팀은 공통 인벤토리를 보유하고 있으며 각 구성원마다 자체 인벤토리가 있습니다. 따라서 흐름은 User1이 항목을 공통 인벤토리로 전송하고 User2가 공통 인벤토리에서 항목을 가져옵니다. 엔티티 인스턴스가 그런 집계를 통해 공유 될 수 있습니까? 아니면 팀을 모델링해야한다는 뜻입니까? transfer() 메소드를 호스팅하는 사람은 누구입니까? – jam01

+0

일반 인벤토리를 사용자 이전 방법으로 전달했을 수 있습니까? – jam01

2

적어도 하나의 집계가 누락되어 있으며이를 집계로 바꾸려고합니다. 도메인 전문가에게 문의 하시어 또는이 누구인지를 알아보십시오.나는 이것이 당신이 "저장소"또는 "데이터베이스"에 의해 행해지는 것을 듣지 않을 것이라고 확신합니다. 이 무언가이 귀하의 집합체가 될 것이며이 방법은 아마도 Transfer 일 것입니다. 이 호출은 transferIntransferOut에서 로그인을 캡슐화합니다. 트랜잭션 프로세스 인 것처럼 보이기 때문에 세 가지 다른 위치에서 수행하고 있습니다. 거래 경계는 합계임을 기억하십시오. 귀하의 저장소가 아닙니다.

+0

글쎄요, 이전을 수행하는 것은 목록입니다. 그래서 그들은 송금 방법을 가지고 있습니다. 서비스를 코디네이터로 사용하므로 인벤토리 엔티티 내에서 지속성을 위해 인벤토리 저장소를 호출 할 필요가 없습니다. 레포 전송 방법에는 비즈니스 로직이 없습니다. 편집 : 답장을 보내 주셔서 감사합니다. 몇 가지 유스 케이스 또는 워크 플로 세부 정보를 제 질문에 추가했습니다. 잘하면 도움이됩니다. – jam01

+1

코드가 없으므로 볼 수 없습니다. 'Transfer '는 비즈니스 용어처럼 들리며'Repository.Transfer (item, item)'은 비즈니스 운영을하는 기술적 인 객체처럼 보입니다. 나는 틀릴 수도 있지만 이것이 어떻게 생겼는지입니다. 그래도 냄새가 나는 한 트랜잭션에 두 개의 집계가 포함되어 있습니다. –

+0

질문에 몇 가지 세부 사항을 추가했습니다. 그리고 레포는 단지 끈기 만 처리합니다. 실제로 데이터 측면에서의 제약입니다. 내가 말한 것에서는 항목을 찾아보고 가리키는 인벤토리 ID를 변경한다는 것뿐입니다. 그래서 진짜 방법은 repo.txChildTo (dest, item)입니다. – jam01