재고사용자서비스를 구성하는 port, adapter

Su hwan Choi·2023년 5월 16일

Actor

어떤 목적을 갖고 있는 존재를 Actor 라고 정의하자.
사용자는 가장기본 Actor이다. 이 예제에서는 재고사용자 에 해당한다.

port

앱의 외부에 있는 Actor는 port를 통해 상호작용 한다. 일반적으로 API 를 생각하면된다. 이 port는 크게 두가지로 분류할 수 있다. 기준은 앱에게 요청을 하는가? 로 앱에게 요청하면 driver port, 앱이 요청하면 driven port이다.

  • driver port
    • 앱에게 요청하는 상호작용
    • 상호작용의 방향은 actor -> 앱
  • driven port
    • 앱이 요청하는 상호작용
    • 상호작용 방향은 앱 -> actor

재고사용처리의 driver port

homeInventorySystem/UseInventoryInput.java

public interface UseInventoryInput {
    /**
     * 재고 사용처리, 수량이 부족할경우 {@link me.mason.management.domain.NotEnoughStockException} 발생
     *
     * @param inventoryId 사용처리하는 재고ID
     * @param quantity    수량
     * @return 사용후 잔여수량
     */
    int usingInventory(InventoryId inventoryId, Integer quantity);
}
  • 재고사용이라는 상호작용을 앱의 외부에서 요청하기 때문에 driver port로 작성했다.
    • 파라미터는 좀더 구체적으로 할 수 있지만, 우선 가장 명확한 정보인, 재고ID와 수량으로 지정했다.

재고데이터 수정의 driven port

homeInventorySystem/ModifyInventoryStateOutput.java

import java.util.Optional;

public interface ModifyInventoryStateOutput {

    //재고수량변경
    void modifyInventory(Inventory inventory);
	  ...
}

재고수량반영은 앱이 actor가 되는 driven port이다

  • 데이터를 저장하는 구체적인 기술은 port만 봐서는 알 수 없다.
    • 구체적인 기술은 앱의 관심사가 아니다.
  • driven port는 앱이 외부로 요청할때 사용되는 port다.

adapter

adapter는 port를 기술적으로 구현한것이다

  • 추상적인 port를 규칙을 구체적으로 어떤 기술을 사용할지 나타낸 것
    • 재고사용 이라는 port를 기술적으로 RestAPI 사용한 방법, gRPC 사용한 방법으로 각각 생성할 수 있다.
      • 기존의 3계층 구조에서는 외부를 무조건 web이라는 패키지로 고정해서 RestAPI가 아닌 다른 기술을 통한 방식은 들어갈 자리가 없었다.
  • driven port의 adapter는 데이터 저장소등이 해당된다.
    • 개인적으로 이 부분이 기존 계층구조와 가장 큰 차이점이라 생각한다.
      • 기존 계층구조는 데이터저장소 이른바 퍼시스턴트 계층이 가장 안쪽에 있었다. 그래서 도메인 객체 는 곧 퍼시스턴트 계층이었다.
    • 하지만 hexagonal 에서는 이 퍼시스턴트 계층도 밖으로 옮길 수 있다.

프로젝트에서 Adapter

재고사용처리port, 재고데이터수정port를 구체적으로 어떤 기술을 사용해서 처리할 것인가?

  • 재고사용처리port는 REST API, 재고데이터수정은 RDB 에 저장되도록 결정했다면
    • 각각 adapter가 port를 참조하도록 작성하면 된다.

재고사용처리Adapter

homeInventorySystem/InventoryRestController.java

@RequestMapping("inventory")
@RestController
public class InventoryRestController {

    private final UseInventoryInput useInventoryInput;

    @Autowired
    public InventoryRestController(UseInventoryInput useInventoryInput) {
        this.useInventoryInput = useInventoryInput;
    }


    @PatchMapping("use/{inventoryId}/{quantity}")
    public void useInventory(
            @PathVariable Long inventoryId,
            @PathVariable Integer quantity) {
        useInventoryInput.usingInventory(new InventoryId(inventoryId), quantity);
    }
}

재고데이터수정 Adapter

homeInventorySystem/InventoryPersistentRepository.java

import java.util.Optional;

@Service
public class InventoryPersistentRepository implements ModifyInventoryStateOutput {

    private final InventoryJpaRepository inventoryJpaRepository;
    private final InventoryMapper inventoryMapper;

    @Autowired
    public InventoryPersistentRepository(
            InventoryJpaRepository inventoryJpaRepository,
            InventoryMapper inventoryMapper) {
        this.inventoryJpaRepository = inventoryJpaRepository;
        this.inventoryMapper = inventoryMapper;
    }

    @Override
    public void modifyInventory(Inventory inventory) {

        InventoryEntity inventoryEntity = inventoryMapper.mapToJpaEntity(inventory);

        inventoryJpaRepository.save(inventoryEntity);
    }

    @Override
    public Optional<Inventory> loadByInventoryId(InventoryId inventoryId) {
        return inventoryJpaRepository.findById(inventoryId.getValue())
                .map(inventoryMapper::mapToDomain);
    }
}

port와 adapter가 주목받는 배경과 MSA, hexagonal 의 적용

이 글에서는 port와 adapter에 대한 내 생각을 기록하려 한다. 다들 이미 알고 있는 내용일 것이지만, 생각을 정리하기 위함이다.

실제로 이전 글에서 언급했듯이, hexagonal 아키텍처의 장점은 대부분의 계층구조 아키텍처가 주장하는 장점과 크게 다르지 않다. 즉, 유연성, 재사용성, 기술과 비즈니스 로직의 분리, 결합도를 낮추고 응집도를 높이는 등의 특성이라 할 수 있다. 그렇다면 왜 갑자기 몇 년 전부터 MSA와 hexagonal이 키워드로 부상했을까?

개인적인 견해로는 개발 조직과 환경의 변화가 가장 근본적인 원인이라고 본다. 현재 개발자 수는 과거에 비해 훨씬 많아졌다. 또한, IT는 이전보다 수익 창출에 더 큰 역할을 하게 되었다. 이전의 IT는 자동화에 초점을 맞추었지만, 현재 IT는 수익 창출에 직접적으로 기여하고 있다.

하지만 이 단계까지는 굳이 MSA가 필요하지 않다고 생각한다. MSA가 주목받기 시작한 결정적인 순간은 Docker와 클라우드의 등장이었다. 이로 인해 하드웨어 수준에서 트래픽을 처리하는 방법이 도입되었고, 이를 통해 서버의 재시작, 추가 등을 비교적 간단하게 할 수 있게 되었다.

그 결과로 기업들은 개발자를 많이 채용하고 개발조직을 확대했다. 하지만, 개발자가 많아질수록 개발은 오히려 더 어려워진다. 클래스는 늘어나고, 의존성은 복잡해진다. 이런 상황에서 모놀리식 구조를 생각 없이 MSA로 전환하려 하면, 동작은 하지만 손을 대기 싫은 코드가 생성된다.

따라서, 나의 결론은 비즈니스 로직이나 업무 이해 없이 바로 MSA나 hexagonal 방식을 적용하는 것은 바람직하지 않다는 것이다. hexagonal 구조를 계속 공부하고, 기존 업무 프로젝트에 일부 적용하면서 느낀 것은 '생각 없이 port를 만들면 실패할 것'이라는 것이다.

0개의 댓글