Cześć,

Po ostatnim poście na temat architektury hexagonalnej zrozumiałem większość rzeczy, ale mam jeszcze specyficzny przypadek związany z logiką dla kilku Use Case'ów, a portami wychodzącymi/outgoing ports i adapterami. Zastanawiam się, które rozwiązanie jest lepsze.

Przykład
Mam domenę Order, oraz 2 Use Case'y: wyciąganie ukończonych zamówień (Complete Orders) i zamówień w trakcie (Orders in Progress).
Mam więc dwa porty wchodzące/incoming ports: SelectCompleteOrders i SelectOrdersInProgress oraz implementacje dla nich: CompleteOrdersSelector i OrdersInProgressSelector
Chcę zapisać te oba typy/ Use Case'y zamówień np. do bazy lub konsoli i zastanawiam się nad prawidłowym stworzeniem portów wychodzacych i adapterów wychodzących do nich.

Logikę w D omain Services: SelectCompleteOrders i SelectOrdersInProgress oczywiście obsługiwałaby OrderFacade, która komunikowałaby się z nimi oraz i adapterami wchodzącymi.

  1. Podejście #1
  • Jeden port wychodzący OrderWriter, który określa Use Case zapisu zamówienia i obsłuży te dwa typy zamówień oraz będzie generyczny dla DTO odpowiedniego dla każdego typu zamówienia:
public interface OrderWriter<T> {
    void write(T order);
}

public class ConsoleCompleteOrderWriter implements OrderWriter<CompleteOrdersDto> {
    @Override
    public void write(CompleteOrdersDto completeOrdersDto) {
    }
}

public class ConsoleOrdersInProgressWriter implements OrderWriter<OrdersInProgressDto> {
    @Override
    public void write(OrdersInProgressDto order) {
    }
}

public class CompleteOrdersDto {
}

public class OrdersInProgressDto {
}
  1. Podejście #2
  • Jeden port wychodzący dla każdego typu zamówienia czyli 1 port wychodzący = 1 Use Case. W tym przypadku będą dwa: CompleteOrdersWriter i OrdersInProgressWriter a jako adaptery wychodzące implementacje tych portów dla każdego typu: ConsoleCompleteOrderWriter i ConsoleOrdersInProgressWriter lub Mongo/PostgresCompleteOrderWriter jeśli byłaby to baza:
public interface CompleteOrdersWriter {
    void write(CompleteOrdersDto completeOrdersDto);
}

public interface OrdersInProgressWriter {
    void write(OrdersInProgressDto ordersInProgressDto);
}

public class ConsoleCompleteOrderWriter implements CompleteOrdersWriter {
    @Override
    public void write(CompleteOrdersDto completeOrdersDto) {
        // Implementacja zapisu dla Complete Orders
    }
}

public class ConsoleOrdersInProgressWriter implements OrdersInProgressWriter {
    @Override
    public void write(OrdersInProgressDto order) {
        // Implementacja zapisu dla Orders In Progress
    }
}

Przykładowa logika i porty wchodzące:

public interface SelectCompleteOrders {
    Set<OrderDto> select(Set<OrderDto> orders);
}

public interface SelectOrdersInProgress {
    Set<OrderDto> select(Set<OrderDto> orders);
}

public class CompleteOrdersSelector implements SelectCompleteOrders {

    private final OrderWriter<CompleteOrdersDto> orderWriter;

    @Override
    public Set<OrderDto> select(Set<OrderDto> orders) {
        // Implementacja logiki dla complete orders
    }
}

public class OrdersInProgressSelector implements SelectOrdersInProgress {

    private final OrderWriter<OrdersInProgressDto> orderWriter;

    @Override
    public Set<OrderDto> select(Set<OrderDto> orders) {
        // Implementacja logiki dla orders in progress
    }
}

Które podejście jest Waszym zdaniem lepsze? Czy lepiej użyć generyków w parametrze metody i operować na jednym porcie jak w podejściu nr 1, czy może tworzyć jeden port wychodzący dla każdego Use Case jak w podejściu nr 2?

Czy możemy w ogóle traktować typ zamówienia np. Complete Order jako osobny Use Case i tym samym tworzyć port wychodzący/interfejs tylko dla niego, czy lepiej użyć generyków i w ogóle nie mysleć w kategorii typu zamówienia jako Use Case?

Z góry dzięki!