Jak rozbijać warstwy w architekturze ściśle warstwowej i promować modułowość bez powodowania zbędnej redundancji?

Otrzymałem zgodę na rozpoczęcie budowy fundamentów pod nową architekturę dla naszej bazy kodów w mojej firmie. Impulsem dla tej inicjatywy jest fakt, że:

  • nasza baza kodów ma ponad dziesięć lat i w końcu łamie się w szwach, gdy próbujemy skalować.
  • Jeśli chcesz je tak nazwać, topowe "warstwy" to bałagan klasycznych ASP i. NET.
  • nasza baza danych jest wypełniona bandą bezbożnych przechowywanych proc, które zawierają tysiące linii logika biznesowa i Walidacja.
  • [14]}wcześniejsi programiści stworzyli "sprytne" rozwiązania, które nie są rozszerzalne, nie nadają się do wielokrotnego użytku i wykazują bardzo oczywiste anty-wzorce; te muszą być wycofane w krótkim czasie.

Odnoszę się do MS Patterns and Practices Architecture Guide dość mocno, ponieważ pracuję nad wstępnym projektem, ale wciąż mam kilka ciągłych pytań, zanim się do czegoś zaangażuję. Zanim przejdę do pytań, oto, co mam do tej pory dla Architektura:

(wysoki poziom)

Wysoki poziom

(warstwy biznesowe i dane w głębi)

Warstwy biznesowe i danych w głębi

Diagramy w zasadzie pokazują, jak mam zamiar podzielić każdą warstwę na wiele zespołów. Więc w tej kandydackiej architekturze, mielibyśmy jedenaście zestawów, nie wliczając w to najwyżej położonych warstw.

Oto podział, z opisem każdego zestawu:

  • Company.Project.Common.OperationalManagement: zawiera komponenty implementujące wyjątek obsługa zasad, rejestrowanie, liczniki wydajności, Konfiguracja i śledzenie.
  • Company.Project.Common.Security: zawiera komponenty, które wykonują uwierzytelnianie, autoryzację i walidację.
  • Company.Project.Common.Communication: zawiera komponenty, które mogą być używane do komunikacji z innymi usługami i aplikacjami (w zasadzie kilka klientów WCF wielokrotnego użytku).
  • Company.Project.Business.Interfaces: zawiera interfejsy i klasy abstrakcyjne, które są używane do interakcji z warstwą biznesową z warstw wysokiego poziomu.
  • Company.Project.Business.Workflows : Zawiera komponenty i logikę związaną z tworzeniem i utrzymywaniem przepływów pracy w biznesie.
  • Company.Project.Business.Components: zawiera komponenty, które zawierają reguły biznesowe i walidację.
  • Company.Project.Business.Entities: zawiera obiekty danych, które są reprezentatywne dla podmiotów gospodarczych na wysokim poziomie. Niektóre z nich mogą być unikalne, niektóre mogą być kompozytami utworzonymi z bardziej ziarnistych jednostek danych z warstwy danych.
  • Company.Project.Data.Interfaces: zawiera interfejsy i klasy abstrakcyjne, które są używane do interakcji z warstwa dostępu do danych w stylu repozytorium.
  • Company.Project.Data.ServiceGateways: zawiera klientów usług i komponenty, które są używane do wywoływania i pobierania danych z zewnętrznych systemów.
  • Company.Project.Data.Components: zawiera komponenty używane do komunikacji z bazą danych.
  • Company.Project.Data.Entities: zawiera znacznie bardziej szczegółowe podmioty, które reprezentują dane biznesowe na niskim poziomie, nadające się do przechowywania w bazie danych lub innym źródle danych w sposób transakcyjny.

Moim zamiarem jest, aby to powinna być ściśle warstwowa (warstwa może komunikować się tylko z warstwą bezpośrednio pod nią), a modułowy podział warstw powinien sprzyjać wysokiej spójności i luźnemu sprzężeniu. Ale nadal mam pewne obawy. Oto moje pytania, które moim zdaniem są na tyle obiektywne, że są odpowiednie tutaj...

  1. Czy moje konwencje nazewnictwa dla każdego modułu i jego odpowiedniego zestawu są zgodne ze standardowymi konwencjami, czy jest inny sposób, w jaki powinienem iść to?
  2. czy korzystne jest rozdzielenie warstw biznesowych i danych na wiele zestawów?
  3. czy korzystne jest posiadanie interfejsów i klas abstrakcyjnych dla każdej warstwy we własnych złożeniach?
  4. najważniejsze - czy korzystne jest posiadanie zestawu "podmiotów" zarówno dla warstwy biznesowej, jak i danych? Moim zmartwieniem jest to, że jeśli włączysz klasy, które będą generowane przez LINQ to SQL wewnątrz komponentów dostępu do danych, to dany encja będzie być reprezentowane w trzech różnych miejscach w bazie kodu. Oczywiście narzędzia takie jak AutoMapper mogą pomóc, ale nadal nie jestem w 100%. Powodem, dla którego je rozbiłem, jest a - egzekwowanie ścisłej architektury warstwowej i B-promowanie luźniejszego sprzężenia między warstwami i zminimalizowanie pękania, gdy nastąpią zmiany w domenie biznesowej za każdym podmiotem. Chciałbym jednak uzyskać wskazówki od ludzi, którzy są bardziej doświadczeni w architekturze niż ja.

Jeśli mógłbym odpowiedzieć na moje pytania lub wskazać mi właściwy kierunek, byłbym bardzo wdzięczny. Dzięki.


Edytuj: Chciałem dodać kilka dodatkowych szczegółów, które wydają się być bardziej istotne po przeczytaniu odpowiedzi pawiana. Tabele bazy danych są również nieświęty bałagan i są quasi-relacyjne, w najlepszym razie. Nie mogę jednak w pełni zrearchitektować bazy danych i wyczyścić danych: najdalej w dół do rdzenia, jaki mogę przejść, to utworzyć nowe przechowywane proc i zacząć deprecjonować stare. Dlatego Skłaniam się do posiadania encji zdefiniowanych wyraźnie w warstwie danych--aby spróbować użyć klas generowanych przez LINQ do SQL (lub jakiegokolwiek innego ORM) jako encji danych po prostu nie wydaje się wykonalne.

Author: Repo Man, 2012-02-07

3 answers

Właśnie zacząłem to samo, więc mam nadzieję, że to pomoże lub przynajmniej wygeneruje więcej komentarzy, a nawet pomoże sobie:)

1. Czy moje konwencje nazewnictwa dla każdego modułu i jego odpowiedniego zestawu są zgodne ze standardowymi konwencjami, czy jest inny sposób, w jaki powinienem to zrobić?

Zgodnie z nazwami MSDN przestrzeni nazw, wydaje się, że jest to ok. Wystąpili:

<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]
For example, Microsoft.WindowsMobile.DirectX.

2.Is czy korzystne jest rozbicie warstwy biznesowej i danych na wiele zestawów?

Zdecydowanie uważam, że korzystne jest rozdzielenie warstw biznesowych i danych na wiele zestawów. Jednak w moim rozwiązaniu stworzyłem tylko dwa zespoły (DataLayer i BusinessLayer). Inne szczegóły jak Interfaces, Workflows, itp. utworzyłbym Katalogi dla pod każdym złożeniem. Myślę, że nie musisz ich rozdzielać na tym poziomie.

3.Is korzystnie wpływa na czy interfejsy i klasy abstrakcyjne dla każdej warstwy mają swoje własne zespoły?

Trochę się zgadza z powyższymi komentarzami.

4.Is czy warto mieć zespół "podmiotów" zarówno dla warstwy biznesowej, jak i danych?

Tak. Powiedziałbym, że Twoje jednostki danych mogą nie mapować bezpośrednio do tego, jaki będzie twój model biznesowy. Podczas przechowywania danych w bazie danych lub na innym nośniku może być konieczna zmiana ustawień, aby były odtwarzane nieźle. Podmioty, które wystawiasz na warstwę usług, powinny być przydatne dla interfejsu użytkownika. Podmioty, których używasz do warstwy dostępu do danych, powinny być użyteczne dla twojego nośnika pamięci. AutoMapper jest zdecydowanie twoim przyjacielem i może pomóc w mapowaniu, jak wspomniałeś. Tak to się kształtuje:

Szczegóły warstwy usług http://i.msdn.microsoft.com/dynimg/IC350997.png

 8
Author: SwDevMan81,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-07 19:18:20

Nie zgodziłbym się z tą standardową architekturą warstwową na rzecz architektury cebulowej.

Tutaj wpisz opis obrazka

Zgodnie z tym mogę spróbować na twoje pytania:

1. Czy moje konwencje nazewnictwa dla każdego modułu i jego odpowiedniego zestawu są zgodne ze standardowymi konwencjami, czy jest inny sposób powinniśmy się tym zająć?

Tak, zgadzam się, że nie jest to zła konwencja, a w zasadzie standard.

2. Czy korzystne jest rozdzielenie warstw biznesowych i danych na wiele zestawów?

Tak, ale mam raczej jeden assembly zwany Domain (Zwykle Core.Domena) i inne o nazwie Data (Rdzeń.Danych). Domain assembly zawiera wszystkie podmioty (zgodnie z Domain-driven-design) wraz z interfejsami repozytoriów, usługami, fabrykami itp... Data assembly odwołuje się do domeny i implementuje konkretne repozytoria za pomocą ORM.

3. Czy to czy warto mieć interfejsy i klasy abstrakcyjne dla każdej warstwy we własnych złożeniach?

W zależności od różnych powodów. W odpowiedzi na poprzednie pytanie wspomniałem o rozdzieleniu interfejsów dla repozytoriów na domenę i konkretnych repozytoriach w zbiorze danych. Daje to czystą domenę bez "zanieczyszczenia" konkretnymi danymi lub inną technologią. Generalnie, opieram swój kod myśląc na poziomie zorientowanym na TDD, wyodrębniając wszystkie zależności od klas czyniących je bardziej użytecznymi, kierując się zasadą SRP i myśląc, co może pójść nie tak, gdy inni ludzie z zespołu korzystają z architektury :) na przykład, jedną z dużych zalet oddzielania na zespoły jest to, że kontrolujesz swoje referencje i wyraźnie stwierdzasz "brak kodu dostępu do danych w domenie!".

4. Czy korzystne jest posiadanie zgromadzenia "podmiotów" zarówno dla warstwy biznesowej, jak i danych?

Nie zgodzę się i powiem nie. Powinieneś mieć swój rdzeń jednostki, i mapować je do bazy danych poprzez ORM. Jeśli masz złożoną logikę prezentacji, możesz mieć coś w rodzaju obiektów ViewModel, które są w zasadzie obiektami z danymi dostosowanymi do reprezentacji w interfejsie użytkownika. Jeśli masz coś w rodzaju sieci pomiędzy, możesz mieć również specjalne obiekty DTO, aby zminimalizować połączenia sieciowe. Ale myślę, że posiadanie danych i oddzielnych podmiotów gospodarczych tylko komplikuje sprawę.

Jeszcze jedna rzecz do dodania tutaj, jeśli jesteś rozpoczynając nową architekturę, a mówisz o aplikacji, która istnieje już od 10 lat, powinieneś rozważyć lepsze narzędzia ORM z LINQ-to-SQL, albo Entity Framework lub NHibernate (moim zdaniem wybieram NHibernate).

Dodam również, że odpowiedź na tyle pytań, ile jest w jednej architekturze aplikacji, jest trudna, więc spróbuj opublikować swoje pytania osobno, a dokładniej. Dla każdej z części architektury (UI, warstwy usług, domeny, bezpieczeństwa i inne wątpliwości) można prowadzić wielostronicowe dyskusje. Pamiętaj również, aby nie przesadzić z architekturą swoich rozwiązań, a z tym komplikuje rzeczy jeszcze bardziej niż potrzebne!

 14
Author: Denis Biondic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-07 19:41:05

1) nazewnictwo jest absolutnie w porządku, tak jak stwierdził SwDevMan81.

2) Oczywiście, jeśli WCF stanie się przestarzały w ciągu kilku lat, będziesz musiał tylko zmienić swój DAL.

3) zasadą jest, aby zadać sobie to proste pytanie: "Czy mogę wymyślić przypadek, w którym będę mądrze z tego korzystać?".
Mówiąc o kontraktach WCF, tak, zdecydowanie umieścić je w osobnym montażu: to jest klucz do dobrego projektu WCF (przejdę do więcej szczegółów).
Kiedy mówimy o interfejs zdefiniowany w AssemblyA, i jest zaimplementowany w AssemblyB, wtedy właściwości / metody opisane w tych interfejsach są używane w AssemblyC, jest dobrze tak długo, jak każda klasa zdefiniowana w AssemblyB jest używana w C poprzez interfejs. W przeciwnym razie będziesz musiał odwołać się zarówno do A, jak i B: przegrywasz.

4) jedynym powodem, dla którego mogę myśleć o poruszaniu się po 3 razy tym samym obiekcie, jest zły projekt: relacje bazy danych były słabo spreparowane, a zatem trzeba dostosować obiekty to wychodzi na coś, z czym możesz pracować.
Jeśli zmienisz architekturę, możesz mieć inny zespół, używany w prawie każdym projekcie, zwany "encjami", który przechowuje obiekty danych. W każdym projekcie miałem na myśli również WCF.

Na marginesie dodam, że usługa WCF powinna być podzielona na 3 zespoły: ServiceContracts, samą usługę i podmioty, o których mówiliśmy. Miałem dobry filmik na ten ostatni temat, ale jest w pracy, podlinkuję jutro!

HTH,

Bab.

Edytuj: tutaj jest wideo.

 2
Author: Louis Kottmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-08 09:53:12