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.
- 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 {
}
- 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!