Tytuł chyba trochę z czapy, ale nic lepszego nie wymyśliłem :D
Robimy apkę w stylu clean/onion.
Taki hipotetyczny case - zaczynamy od tego, że użytkownik ma możliwość wyeksportowania eksportu jakichś biznesowych danych w postaci CSV.
No to w warstwie aplikacyjnej definiujemy model na dane do eksportu i interfejs do eksportera, coś w stylu:
public record ExportData(Employee employee, IEnumerable<SalesDetails> salesDetails);
public interface ICsvReportExporter
{
public Stream Export(ExportData data);
}
W warstwie infrastruktury implementujemy ten interfejs:
internal CsvReportExporter : ICsvReportExporter
{
public Stream Export(ExportData data) { //korzystajac z jakiejs libki robimy mapowania i eksport do CSV }
}
Handler aplikacyjny najpierw zbiera potrzebne dane, tworzy model do eksportu (ExportData
) i wywołuje interfejs. Wszystko wygląda git.
I tutaj już pierwsza rzecz która przychodzi na myśl to czy ten interfejs w takiej postaci to dobry poziom abstrakcji i czy na pewno powinien się nazywać ICsvReportExporter. Czy nie jest to zbyt mało ogólna abstrakcja, bo od razu sugeruje, że to potrafi eksport tylko do CSV - może eksporter powinien być interfejsem IReportExporter, a to czy CSV, czy nie z punktu widzenia warstwy aplikacyjjnej powinno być przezroczyste.
Z drugiej strony w tym przypadku to żaden szczegół implementacyjny tylko raczej wymaganie biznesowe - format jest z góry ustalony i to nie jest coś co podmienimy jako zależność czysto techniczną bez wpływu na funkcjonalność aplikacji (za zależność czysto techniczną rozumiem np. wewnętrzny storage czyli słynne repozytorium, albo broker wiadomości - Rabbit/Azure Service Bus, w tych przypadkach to rzeczywiście szczegół czysto implementacyjny zazwyczaj przezroczysty dla użytkownika końcowego/funkcjonalności aplikacji).
Później sytuacja rozwija się tak, że użytkownik ma możliwość wyeksportowania tych samych danych, ale w innej formie - Excel - dane dokładnie te same jak w CSV, a wstawione po prostu jako kolumny w Excelu.
I albo teraz tworzymy nowy interfejs (IExcelReportExporter
), albo jednak stwierdzamy, że przykrywamy to jednym interfejsem:
public enum ExportType
{
CSV
Excel,
}
public interface IReportExporter
{
public Stream Export(ExportData data, ExportType type)
}
Sam interfejs dalej przyjmuje parametr dotyczący typu eksportu, bo jak pisałem wyżej - w takim przypadku obsługiwanie kilku typów eksportów to funkcjonalność aplikacyjna (to użytkownik w aplikacji wybiera w jakim formacie chce wyeksportować dane), a nie szczegół czysto implementacyjny.
Ogólnie nowy interfejs wygląda ok.
Ale dostajemy wymaganie eksportu do kolejnego formatu, niech to będzie PDF. Jako, że PDF umożliwia eksport w bardziej przystępnej postaci - z jakiegoś biznesowego powodu eksport do PDF wymaga trochę więcej danych/danych w innym formacie.
I co robimy teraz?
Albo znowu wracamy do tego, że tworzymy nowy interfejs pod eksport PDF - IPdfExporter
i warstwa aplikacyjna sobie jakoś tam ifuje na podstawie inputu od użytkownika i wyciąga inne dane dla eksportów CSV/Excel, a inne dla PDF i następnie wywołuje odpowiedni interfejs, albo wprowadzamy jakieś kolejne (chyba trochę pokraczne) abstrakcje tak żeby mieć jeden interfejs do eksportu, który w zależności od typu musi przyjąć trochę inne dane - albo rzeźbienie na słownikach, albo jakieś hierarchie klas i rzutowanie w konkretnych implementacjach.
No więc wracając do pytania z wątku - byście to chowali za jednym interfejsem wprowadzając dodatkowe abstrakcje (może jakiś IExportDataProvider, który następnie jest przekazywany do metody Export z interfejsu), czy jednak zrobili oddzielny interfejs?