어떤 목적을 갖고 있는 존재를 Actor 라고 정의하자.
사용자는 가장기본 Actor이다. 이 예제에서는 재고사용자 에 해당한다.
앱의 외부에 있는 Actor는 port를 통해 상호작용 한다. 일반적으로 API 를 생각하면된다. 이 port는 크게 두가지로 분류할 수 있다. 기준은 앱에게 요청을 하는가? 로 앱에게 요청하면 driver port, 앱이 요청하면 driven 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);
}
homeInventorySystem/ModifyInventoryStateOutput.java
import java.util.Optional;
public interface ModifyInventoryStateOutput {
//재고수량변경
void modifyInventory(Inventory inventory);
...
}
재고수량반영은 앱이 actor가 되는 driven port이다
adapter는 port를 기술적으로 구현한것이다
재고사용처리port, 재고데이터수정port를 구체적으로 어떤 기술을 사용해서 처리할 것인가?
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);
}
}
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에 대한 내 생각을 기록하려 한다. 다들 이미 알고 있는 내용일 것이지만, 생각을 정리하기 위함이다.
실제로 이전 글에서 언급했듯이, hexagonal 아키텍처의 장점은 대부분의 계층구조 아키텍처가 주장하는 장점과 크게 다르지 않다. 즉, 유연성, 재사용성, 기술과 비즈니스 로직의 분리, 결합도를 낮추고 응집도를 높이는 등의 특성이라 할 수 있다. 그렇다면 왜 갑자기 몇 년 전부터 MSA와 hexagonal이 키워드로 부상했을까?
개인적인 견해로는 개발 조직과 환경의 변화가 가장 근본적인 원인이라고 본다. 현재 개발자 수는 과거에 비해 훨씬 많아졌다. 또한, IT는 이전보다 수익 창출에 더 큰 역할을 하게 되었다. 이전의 IT는 자동화에 초점을 맞추었지만, 현재 IT는 수익 창출에 직접적으로 기여하고 있다.
하지만 이 단계까지는 굳이 MSA가 필요하지 않다고 생각한다. MSA가 주목받기 시작한 결정적인 순간은 Docker와 클라우드의 등장이었다. 이로 인해 하드웨어 수준에서 트래픽을 처리하는 방법이 도입되었고, 이를 통해 서버의 재시작, 추가 등을 비교적 간단하게 할 수 있게 되었다.
그 결과로 기업들은 개발자를 많이 채용하고 개발조직을 확대했다. 하지만, 개발자가 많아질수록 개발은 오히려 더 어려워진다. 클래스는 늘어나고, 의존성은 복잡해진다. 이런 상황에서 모놀리식 구조를 생각 없이 MSA로 전환하려 하면, 동작은 하지만 손을 대기 싫은 코드가 생성된다.
따라서, 나의 결론은 비즈니스 로직이나 업무 이해 없이 바로 MSA나 hexagonal 방식을 적용하는 것은 바람직하지 않다는 것이다. hexagonal 구조를 계속 공부하고, 기존 업무 프로젝트에 일부 적용하면서 느낀 것은 '생각 없이 port를 만들면 실패할 것'이라는 것이다.