Projektowanie modułowej aplikacji serwisowej

11

Zastanawiam się nad stworzeniem nowego rozwiązania, które z natury jest bardzo modułowe i chciałbym stworzyć strukturę obsługującą ten projekt, aby umożliwić łatwą rozbudowę w przyszłości, wyraźne rozdzielenie problemów, licencjonowanie według modułów itp. Większość tego, co mam znalezione w Internecie informacje o aplikacjach modułowych lub złożonych są zorientowane na interfejs użytkownika, koncentrują się na Silverlight, WPF itp. W moim przypadku opracowuję aplikację usługi WCF, która będzie używana przez innych programistów pracujących nad różnymi projektami interfejsu użytkownika.

tło

Celem mojego projektu jest stworzenie scentralizowanego źródła logiki biznesowej / domeny do obsługi kilku naszych aplikacji biznesowych, które obecnie duplikują procesy, reguły itp. I choć nie wszystkie korzyści wynikające z modułowości zostaną osiągnięte, chciałbym wykorzystać możliwość ustanowienia ram, które wykorzystają te części, z których możemy skorzystać.

Rozpocząłem projektowanie od spojrzenia na interfejs API udostępniony przez aplikację serwisową. Oczywiste jest, że usługi można podzielić według modularnych linii, które rozważałem. Na przykład będę mieć usługi FinanceService, InventoryService, PersonnelService itp., Które grupują moje operacje usługowe, aby zapewnić wysoką spójność w interfejsie API przy jednoczesnym utrzymaniu niskiego poziomu sprzężenia, tak aby klienci musieli korzystać tylko z usług związanych z ich aplikacją.

Ma dla mnie sens, że mogę mieć osobne moduły dla każdej z tych usług, takie jak MyApp.Finance, MyApp.Inventory, My.Personnel i tak dalej. Zagadnienia przekrojowe i typy udostępnione byłyby we wspólnym zestawie MyApp. Stąd jestem trochę związany.

(Och, powinienem wspomnieć, że użyję kontenera IoC do Dependency Injection, aby utrzymać luźne powiązanie aplikacji. Nie wspomnę o którym, ponieważ nie chcę otwierać pudełka Pandory!)

W MyApp.ServiceHost utworzę plik hosta usługi (.svc) odpowiadający każdemu modułowi, na przykład FinanceService.svc. Host usługi potrzebuje nazwy usługi, która odpowiada informacjom w pliku konfiguracyjnym zawierającym interfejs definiujący umowę serwisową. Konfiguracja IoC jest następnie używana do mapowania konkretnej implementacji interfejsu do użycia.

1. Czy warstwa usługi powinna implementować interfejs API i przekazywać moduły, czy moduły powinny być samodzielne (ponieważ zawierają wszystko, co związane z tym modułem, w tym implementację usługi)?

Jednym ze sposobów rozwiązania tego problemu jest posiadanie „modułu” MyApp.Services, który zawiera realizację umów serwisowych. Każda klasa usług po prostu deleguje do innej klasy w odpowiednim module, który zawiera logikę domeny dla operacji. Na przykład klasa WCS FinanceService w MyApp.Services deleguje do innego interfejsu, który jest zaimplementowany w module Finance w celu przeprowadzenia operacji. Pozwoliłoby mi to zachować cienką fasadę usługi i „wtyczkę” implementacji do implementacji usługi i wyeliminować konieczność martwienia się modułów na przykład o WCF.

Z drugiej strony, być może preferowane jest, aby każdy moduł był samodzielny, ponieważ ma interfejs i implementację. Host usług odnosi się do interfejsu umowy serwisowej znalezionego w module, a IoC jest skonfigurowany do korzystania z odpowiedniej implementacji również z modułu. Oznacza to, że dodawanie nowego modułu można wykonać bez żadnych zmian w warstwie usług oprócz dodania nowego pliku .svc i informacji konfiguracyjnych IoC.

Zastanawiam się nad tym, czy przejdę ze standardowego WCF na interfejs usługi RESTful, a może przejdę do usług RIA lub czegoś takiego. Jeśli każdy moduł zawiera realizację umowy serwisowej, muszę wprowadzić zmiany w każdym module, jeśli zmienię technologię lub podejście serwisowe. Ale jeśli fasada jest własnym modułem, muszę tylko zamienić tę część, aby dokonać zmiany. Moduły musiałyby wówczas zaimplementować inny zestaw umów (interfejsów), prawdopodobnie zdefiniowany we wspólnym zestawie ???

2. Jaki jest najlepszy sposób obsługi współdzielenia zasobów między modułami i / lub zależności między modułami?

Weźmy na przykład operację odbierania. Na pierwszy rzut oka sensowne jest, że trafia ono do modułu zapasów, ponieważ przyjmowanie towarów jest funkcją zapasów. Istnieje jednak również aspekt finansowy, ponieważ musimy wygenerować pokwitowanie i autoryzować płatność.

Z jednej strony spodziewałbym się, że użyję pewnego rodzaju zdarzeń / wiadomości w domenie do komunikowania operacji. Moduł zapasów wywołuje towary, które są obsługiwane przez moduł finansowy w celu wygenerowania pokwitowania i zainicjowania procesu płatności. Oznacza to jednak, że moduł finansowy musi wiedzieć o otrzymanych pozycjach magazynowych. Mogę po prostu odnieść się do nich według identyfikatora, ale co, jeśli potrzebuję dodatkowych informacji dotyczących paragonu, takich jak nazwa i / lub opis, koszt jednostkowy itp.? Czy ma sens, aby każdy moduł miał własną wersję elementu ekwipunku, który został zaprojektowany w celu zaspokojenia potrzeb tego modułu? W takim przypadku moduł finansowy musiałby samodzielnie sprawdzić pozycje zapasów podczas obsługi towarów otrzymanych.

...

Użyłem tego przykładu celowo, ponieważ dużo pracowałem z różnymi systemami ERP i wiem, że są one zaprojektowane z tego rodzaju modułowością - po prostu nie wiem jak. Nie wspominam też wyraźnie o tym powyżej, ale wolę podążać za zasadami projektowania opartego na domenie podczas projektowania rozwiązania i wierzę, że ten typ modułowości pasuje właśnie do tego obszaru.

Każda pomoc w owinięciu mojej głowy jest bardzo doceniana.

SonOfPirate
źródło
1
Przepraszam. Nie mogę znaleźć pytania w całym myśleniu. Jakie jest pytanie?
S.Lott,
1
Poszukaj znaków zapytania. Jest kilka punktów, w których oferuję opcje, ale nie zakładam rozwiązania i kilku konkretnych pytań, które pomogą poprowadzić proces właściwą ścieżką.
SonOfPirate
1
@SonOfPirate: Przepraszamy. Miałem nadzieję, że poświęcisz trochę czasu na podkreślenie lub podkreślenie pytań, aby inni mogli Ci pomóc.
S.Lott
4
Zgadzam się z S.Lott. Przepraszam, ale to nie jest pytanie, to strumień świadomości. Możemy Ci pomóc znacznie lepiej, jeśli ograniczysz to do jednego lub dwóch skoncentrowanych pytań i spróbujesz oddzielić obawy architektoniczne od szczegółów implementacyjnych.
Aaronaught
1
@SonOfPirate: „Architektura” dotyczy struktury i przekazywania tej struktury innym programistom. Zaczyna się od opisu. Musi osiągnąć poziom jasności i przejrzystości, który pozwala innym programistom całkowicie go „zdobyć” i przestrzegać zasad projektowania architektonicznego we wszystkim, co robią.
S.Lott

Odpowiedzi:

2

Czy ma sens, aby każdy moduł miał własną wersję elementu ekwipunku, który został zaprojektowany w celu zaspokojenia potrzeb tego modułu? W takim przypadku moduł finansowy musiałby samodzielnie sprawdzić pozycje zapasów podczas obsługi towarów otrzymanych. Gdzie mam wytyczyć granicę między modułowością a potrzebą udostępniania informacji?

Zawsze będziesz musiał zaakceptować kompromisy. To wszystko, co naprawdę można powiedzieć na twoje pytania. Dobrą praktyką było utrzymywanie jednostek w osobnym zestawie, dlatego wiele aplikacji utrzymuje je we wspólnej bibliotece. Ale to oznacza, że ​​nie ma (fizycznych) granic logiki biznesowej. Przeprowadzanie własnych wyszukiwań oznacza dużo zbędnego kodu, który zaimplementowałbym tylko wtedy, gdy masz specjalne wymagania w obu modułach. Oczywiście, patrząc na to z architektonicznego punktu widzenia, moduł finansowy i moduł ekwipunku i tak muszą współdzielić zależności. Moduł finansowy jest najprawdopodobniej zależny od modułu zapasów. Jeśli wymagania pozwalają na to, aby jeden moduł zależał od innego modułu, powiedziałbym, że można enkapsulować jednostki w modułach, do których należą one najbardziej. Ty' Muszę znaleźć równowagę między fizycznym rozróżnieniem (wspólne zależności) a utrzymaniem. Zbyt wiele współdzielonych zależności może spowodować koszmar konserwacji wersji. Ponadto z ORM będą kłopoty, jeśli chcesz mapować nowe relacje do istniejących encji lub rozszerzyć je z modułów zależnych od modułu zawierającego encję.

Jeśli nie korzystasz z ORM, pamiętaj, że wszystkie moduły i tak prawdopodobnie współużytkują tę samą bazę danych, jak to często bywa. Możesz użyć tego jako wspólnej płaszczyzny.

Możesz również zachować czystość danych (np. W zakresie CSV / zestawów wyników) i skorzystać z centralnej usługi przesyłania wiadomości, aby powiadomić zarejestrowane moduły o nowych danych wraz z niektórymi meta informacjami. Oznacza to brak rzeczywistych zależności, ale poświęca bezpieczeństwo typu i umowy i mogą wystąpić problemy ze zniekształconymi danymi. Myślę, że tyle właśnie robi to dużych systemów ERP.

Krótko mówiąc, musisz zdecydować, jakiego rodzaju interfejsu chcesz. Większa modułowość prowadzi do mniejszej liczby kontraktów i odwrotnie.

Prawdopodobnie używam biblioteki współdzielonej dla moich obiektów danych i centralnej usługi przesyłania wiadomości z wzorcem publikowania subcibe, co wydaje mi się najlepszym rozwiązaniem.

Sokół
źródło
Zgadzam się również z tym - wiadomości + pub / sub i biblioteki współdzielone dla kontraktów DTO (wewnętrzne repozytorium nuget). Zastanawiałem się nad tymi samymi pytaniami co @ SonOfPirate, dopóki ten artykuł nie przedstawił mi tego w perspektywie: msdn.microsoft.com/en-us/library/bb245678.aspx
diegohb