Myślałem o tym jakiś czas temu i niedawno pojawił się ponownie, ponieważ mój sklep tworzy swoją pierwszą prawdziwą aplikację internetową w języku Java.
Jako wprowadzenie widzę dwie główne strategie nazewnictwa pakietów. (Żeby było jasne, nie odnoszę się do całej części tego „domena.firma.project”, mówię o konwencji pakietu poniżej.) W każdym razie, widzę konwencje nazewnictwa pakietów:
Funkcjonalne: nazywanie pakietów zgodnie z ich funkcją architektoniczną, a nie ich tożsamością zgodnie z domeną biznesową. Innym terminem na to może być nazywanie według „warstwy”. Tak więc miałbyś pakiet * .ui, pakiet * .domain i * .orm. Twoje pakiety to poziome plasterki, a nie pionowe.
Jest to znacznie częstsze niż nazewnictwo logiczne. Właściwie nie wierzę, żebym kiedykolwiek widział lub słyszał o projekcie, który to robi. To oczywiście sprawia, że jestem nieufny (trochę tak, jakbyś pomyślał, że znalazłeś rozwiązanie problemu NP), ponieważ nie jestem zbyt mądry i zakładam, że każdy musi mieć świetne powody, aby robić to tak, jak robią. Z drugiej strony nie sprzeciwiam się temu, by ludzie po prostu tęsknili za słoniem w pokoju i nigdy nie słyszałem prawdziwego argumentu za takim nazewnictwem paczek. Po prostu wydaje się, że jest to de facto standard.
Logiczne: nazywanie pakietów zgodnie z tożsamością domeny biznesowej i umieszczanie w tym pakiecie każdej klasy, która ma do czynienia z tym pionowym wycinkiem funkcjonalności.
Nigdy o tym nie widziałem ani nie słyszałem, jak wspomniałem wcześniej, ale ma to dla mnie mnóstwo sensu.
Zwykle podchodzę do systemów w pionie, a nie w poziomie. Chcę wejść i rozwijać system obsługi zamówień, a nie warstwę dostępu do danych. Oczywiście istnieje duża szansa, że podczas tworzenia tego systemu dotknę warstwy dostępu do danych, ale chodzi o to, że nie myślę o tym w ten sposób. Oznacza to oczywiście, że kiedy otrzymuję zlecenie zmiany lub chcę zaimplementować jakąś nową funkcję, byłoby miło nie musieć wędrować w grupie pakietów, aby znaleźć wszystkie powiązane klasy. Zamiast tego po prostu zaglądam do pakietu X, ponieważ to, co robię, ma związek z X.
Z punktu widzenia programisty uważam, że główną zaletą jest dokumentowanie przez pakiety domeny biznesowej, a nie architektury. Wydaje mi się, że domena jest prawie zawsze tą częścią systemu, która jest trudniejsza do zrozumienia, gdzie architektura systemu, zwłaszcza w tym momencie, staje się prawie przyziemna w jego implementacji. Fakt, że mogę dojść do systemu z tego typu konwencją nazewnictwa i od razu z nazewnictwa paczek wiem, że zajmuje się on zamówieniami, klientami, przedsiębiorstwami, produktami itp. Wydaje się cholernie przydatny.
Wygląda na to, że pozwoliłoby to znacznie lepiej wykorzystać modyfikatory dostępu Javy. Pozwala to na znacznie bardziej przejrzyste definiowanie interfejsów w podsystemach, a nie w warstwach systemu. Więc jeśli masz podsystem zamówień, który chcesz, aby był przezroczysty trwały, teoretycznie możesz po prostu nigdy nie pozwolić nikomu innemu wiedzieć, że jest trwały, ponieważ nie musisz tworzyć publicznych interfejsów do jego klas trwałości w warstwie dao i zamiast tego pakować klasę dao w tylko z zajęciami, którymi się zajmuje. Oczywiście, jeśli chcesz ujawnić tę funkcjonalność, możesz zapewnić jej interfejs lub upublicznić. Po prostu wygląda na to, że wiele z tego tracisz, dzieląc pionowy fragment funkcji systemu na wiele pakietów.
Przypuszczam, że jedną wadą, którą widzę, jest to, że sprawia, że zrywanie warstw jest trochę trudniejsze. Zamiast po prostu usunąć lub zmienić nazwę pakietu, a następnie upuścić nowy w miejscu z alternatywną technologią, musisz wejść i zmienić wszystkie klasy we wszystkich pakietach. Jednak nie widzę, żeby to była wielka sprawa. Może to wynikać z braku doświadczenia, ale muszę sobie wyobrazić, że ilość razy, kiedy wymieniasz technologie, blednie w porównaniu z ilością razy, kiedy wchodzisz i edytujesz pionowe wycinki funkcji w swoim systemie.
Więc myślę, że wtedy padłoby pytanie, jak nazwać swoje paczki i dlaczego? Proszę zrozum, że niekoniecznie myślę, że natknąłem się na złotą gęś czy coś tutaj. Jestem całkiem nowy w tym wszystkim, mając głównie doświadczenie akademickie. Jednak nie mogę dostrzec luk w moim rozumowaniu, więc mam nadzieję, że wszyscy to potrafią, abym mógł przejść dalej.
źródło
Odpowiedzi:
W przypadku projektowania opakowań najpierw dzielę według warstw, a następnie według innych funkcji.
Istnieją dodatkowe zasady:
Na przykład w przypadku aplikacji internetowej możesz mieć następujące warstwy w warstwie aplikacji (od góry do dołu):
Oto kilka dodatkowych reguł dotyczących wynikowego układu pakietu:
<prefix.company>.<appname>.<layer>
<root>.<logic>
<root>.private
Oto przykładowy układ.
Warstwa prezentacji jest podzielona według technologii wyświetlania i opcjonalnie (grup) aplikacji.
Warstwa aplikacji jest podzielona na przypadki użycia.
Warstwa usług jest podzielona na domeny biznesowe, na które wpływa logika domeny w warstwie zaplecza.
Warstwa integracji jest podzielona na „technologie” i obiekty dostępu.
Zaletą takiego oddzielania pakietów jest to, że łatwiej jest zarządzać złożonością oraz zwiększa możliwości testowania i ponownego wykorzystania. Chociaż wydaje się, że jest to duże obciążenie, z mojego doświadczenia wynika, że jest to bardzo naturalne i wszyscy pracujący nad tą strukturą (lub podobną) odbierają ją w ciągu kilku dni.
Dlaczego uważam, że podejście wertykalne nie jest tak dobre?
W modelu warstwowym kilka różnych modułów wysokiego poziomu może używać tego samego modułu niższego poziomu. Na przykład: możesz zbudować wiele widoków dla tej samej aplikacji, wiele aplikacji może korzystać z tej samej usługi, wiele usług może korzystać z tej samej bramy. Sztuczka polega na tym, że podczas przechodzenia przez warstwy zmienia się poziom funkcjonalności. Moduły w bardziej szczegółowych warstwach nie odwzorowują 1-1 na modułach z bardziej ogólnej warstwy, ponieważ poziomy funkcjonalności, które wyrażają, nie odwzorowują 1-1.
Kiedy używasz podejścia wertykalnego do projektowania opakowań, tj. Najpierw dzielisz według funkcjonalności, następnie wsuwasz wszystkie elementy składowe o różnych poziomach funkcjonalności do tej samej „kurtki funkcjonalnej”. Możesz zaprojektować swoje ogólne moduły dla bardziej szczegółowych. Ale to narusza ważną zasadę, że bardziej ogólna warstwa nie powinna wiedzieć o bardziej szczegółowych warstwach. Na przykład warstwa usług nie powinna być modelowana na podstawie koncepcji z warstwy aplikacji.
źródło
Uważam, że trzymam się zasad projektowania opakowań wujka Boba . Krótko mówiąc, klasy, które mają być ponownie używane razem i razem zmieniane (z tego samego powodu, np. Zmiana zależności lub zmiana struktury), powinny być umieszczone w tym samym pakiecie. IMO, podział funkcjonalny miałby większe szanse na osiągnięcie tych celów niż podział branżowy / biznesowy w większości aplikacji.
Na przykład poziomy wycinek obiektów domeny może być ponownie wykorzystany przez różne rodzaje front-endów, a nawet aplikacje, a poziomy fragment frontonu sieci Web prawdopodobnie zmieni się razem, gdy konieczna będzie zmiana podstawowej struktury sieciowej. Z drugiej strony łatwo sobie wyobrazić efekt falowania tych zmian w wielu pakietach, jeśli klasy z różnych obszarów funkcjonalnych są zgrupowane w tych pakietach.
Oczywiście nie wszystkie rodzaje oprogramowania są takie same, a podział pionowy może mieć sens (jeśli chodzi o osiągnięcie celów ponownego wykorzystania i możliwości zmiany) w niektórych projektach.
źródło
Zwykle występują oba poziomy podziału. Od góry znajdują się jednostki rozmieszczające. Nazywa się je „logicznie” (w twoim rozumieniu, pomyśl o funkcjach Eclipse). Wewnątrz jednostki wdrożeniowej masz funkcjonalny podział pakietów (pomyśl o wtyczkach Eclipse).
Na przykład, funkcja jest
com.feature
, i składa się zcom.feature.client
,com.feature.core
icom.feature.ui
wtyczek. Wewnątrz wtyczek mam bardzo mały podział na inne pakiety, chociaż to też nie jest niezwykłe.Aktualizacja: Przy okazji, Juergen Hoeller mówi o organizacji kodu w InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen jest jednym z architektów Springa i dużo o tym wie.
źródło
Większość projektów java, nad którymi pracowałem, dzieli pakiety java najpierw funkcjonalnie, a potem logicznie.
Zwykle części są wystarczająco duże, aby zostały podzielone na oddzielne artefakty kompilacji, w których można umieścić podstawową funkcjonalność w jednym słoiku, interfejs API w innym, elementy interfejsu WWW w pliku wojennym itp.
źródło
Pakiety mają być kompilowane i dystrybuowane jako całość. Rozważając, jakie klasy należą do pakietu, jednym z kluczowych kryteriów są jego zależności. Od jakich innych pakietów (w tym bibliotek innych firm) zależy ta klasa. Dobrze zorganizowany system połączy w pakiet klasy o podobnych zależnościach. Ogranicza to wpływ zmiany w jednej bibliotece, ponieważ będzie od niej zależeć tylko kilka dobrze zdefiniowanych pakietów.
Wygląda na to, że Twój logiczny, pionowy system może mieć tendencję do „rozmazania” zależności w większości pakietów. Oznacza to, że jeśli każda funkcja jest spakowana jako wycinek pionowy, każdy pakiet będzie zależał od każdej używanej biblioteki innej firmy. Każda zmiana w bibliotece prawdopodobnie wpłynie na cały system.
źródło
Osobiście wolę logiczne grupowanie klas, które zawierają podpakiet dla każdego funkcjonalnego uczestnictwa.
Cele pakowania
W pakietach chodzi w końcu o grupowanie rzeczy - ideą jest to, że powiązane ze sobą klasy żyją blisko siebie. Jeśli mieszkają w tym samym pakiecie, mogą skorzystać z pakietu prywatnego, aby ograniczyć widoczność. Problem polega na tym, że wrzucanie całego widoku i wytrwałości do jednego pakietu może prowadzić do pomieszania wielu klas w jeden pakiet. Następną rozsądną rzeczą do zrobienia jest utworzenie odpowiednio widoku, trwałości, pakietów użytkowych i klas refaktoryzacji. Niestety chroniony i prywatny zakres pakietu nie wspiera koncepcji obecnego pakietu i pakietu podrzędnego, ponieważ pomogłoby to w egzekwowaniu takich zasad widoczności.
Widzę teraz wartość w separacji poprzez funkcjonalność, ponieważ jaka jest wartość grupowania wszystkich rzeczy związanych z widokiem. Rzeczy w tej strategii nazewnictwa zostają rozłączone z niektórymi klasami w widoku, podczas gdy inne trwają i tak dalej.
Przykład mojej logicznej struktury opakowania
Dla ilustracji nazwijmy dwa moduły - niewłaściwie używajmy modułu nazw jako koncepcji grupującej klasy w określonej gałęzi drzewa pakietów.
apple.model apple.store banana.model banana.storeZalety
Klient korzystający z Banana.store.BananaStore ma dostęp tylko do funkcji, które chcemy udostępnić. Wersja hibernacji jest szczegółem implementacji, którego nie muszą być świadomi ani nie powinni widzieć tych klas, ponieważ dodają bałagan do operacji przechowywania.
Inne zalety logiczne v funkcjonalne
Im dalej w górę w kierunku korzenia, tym szerszy staje się zakres i rzeczy należące do jednego pakietu zaczynają wykazywać coraz więcej zależności od elementów należących do ich modułów. Gdyby zbadać na przykład moduł „bananowy”, większość zależności ograniczałaby się do tego modułu. W rzeczywistości do większości pomocników w kategorii „banana” nie byłoby w ogóle odwołań poza zakresem tego pakietu.
Dlaczego funkcjonalność?
Jaką wartość osiąga się zbierając rzeczy w oparciu o funkcjonalność. W takim przypadku większość klas jest od siebie niezależnych i nie ma potrzeby korzystania z prywatnych metod lub klas pakietu. Refaktoryzacja ich do własnych podpakietów niewiele zyskuje, ale pomaga zmniejszyć bałagan.
Developer zmiany w systemie
Kiedy programiści mają wprowadzić zmiany, które są nieco bardziej niż trywialne, wydaje się głupie, że potencjalnie mają zmiany, które obejmują pliki ze wszystkich obszarów drzewa pakietów. Przy logicznym podejściu strukturalnym ich zmiany są bardziej lokalne w tej samej części drzewa pakietów, co wydaje się właściwe.
źródło
Swoje miejsce mają zarówno funkcjonalne (architektoniczne), jak i logiczne (cechowe) podejście do pakowania. Wiele przykładowych aplikacji (tych, które można znaleźć w podręcznikach itp.) Opiera się na funkcjonalnym podejściu polegającym na umieszczaniu prezentacji, usług biznesowych, mapowania danych i innych warstw architektonicznych w oddzielnych pakietach. W przykładowych aplikacjach każdy pakiet ma często tylko kilka lub tylko jedną klasę.
To początkowe podejście jest w porządku, ponieważ wymyślony przykład często służy do: 1) koncepcyjnego odwzorowania architektury prezentowanej struktury, 2) odbywa się to w jednym logicznym celu (np. Dodawanie / usuwanie / aktualizowanie / usuwanie zwierząt domowych z kliniki) . Problem w tym, że wielu czytelników traktuje to jako standard bez granic.
Ponieważ aplikacja „biznesowa” rozszerza się i obejmuje coraz więcej funkcji, stosowanie podejścia funkcjonalnego staje się ciężarem. Chociaż wiem, gdzie szukać typów opartych na warstwie architektury (np. Kontrolery sieciowe w pakiecie "web" lub "ui", itp.), Opracowanie pojedynczej funkcji logicznej zaczyna wymagać przeskakiwania tam i z powrotem między wieloma pakietami. Jest to co najmniej uciążliwe, ale jest gorsze niż to.
Ponieważ logicznie powiązane typy nie są pakowane razem, interfejs API jest zbyt rozpowszechniony; interakcja między logicznie powiązanymi typami musi być „publiczna”, aby typy mogły importować i współdziałać ze sobą (możliwość minimalizacji do domyślnej / widoczności pakietu zostaje utracona).
Jeśli tworzę bibliotekę frameworkową, moje pakiety będą zgodne z funkcjonalnym / architektonicznym podejściem do pakowania. Moi konsumenci API mogą nawet docenić fakt, że ich instrukcje importowania zawierają intuicyjny pakiet nazwany na podstawie architektury.
I odwrotnie, podczas tworzenia aplikacji biznesowej będę pakować według funkcji. Nie mam problemu z umieszczeniem Widget, WidgetService i WidgetController w tym samym pakiecie „ com.myorg.widget. ”, A następnie skorzystaniem z domyślnej widoczności (i mniejszej liczby instrukcji importu oraz zależności między pakietami).
Istnieją jednak przypadki krzyżowe. Jeśli moja usługa WidgetService jest używana przez wiele domen logicznych (funkcji), mogę utworzyć pakiet „ com.myorg.common.service. ”. Istnieje również duża szansa, że utworzę klasy z zamiarem ponownego wykorzystania w różnych funkcjach i ostatecznie otrzymam takie pakiety, jak „ com.myorg.common.ui.helpers. ” I „ com.myorg.common.util. ”. Mogę nawet skończyć przenosząc wszystkie późniejsze „wspólne” klasy do osobnego projektu i dołączając je do mojej aplikacji biznesowej jako zależność myorg-commons.jar.
źródło
To zależy od stopnia szczegółowości Twoich procesów logicznych?
Jeśli są one autonomiczne, często masz dla nich nowy projekt w kontroli źródła, a nie nowy pakiet.
Projekt, nad którym obecnie pracuję, zmierza w kierunku logicznego podziału, jest pakiet dla aspektu jython, pakiet dla silnika reguł, pakiety dla foo, bar, binglewozzle, itp. Szukam parserów specyficznych dla XML / writers dla każdego modułu w tym pakiecie, zamiast mieć pakiet XML (co zrobiłem wcześniej), chociaż nadal będzie istniał podstawowy pakiet XML, do którego idzie współdzielona logika. Jednym z powodów tego jest jednak to, że może być rozszerzalny (wtyczki), a zatem każda wtyczka będzie musiała również zdefiniować swój kod XML (lub bazy danych itp.), Więc centralizacja może spowodować później problemy.
Ostatecznie wydaje się, że jest to najbardziej sensowne dla danego projektu. Myślę jednak, że łatwo jest spakować zgodnie z liniami typowego diagramu warstwowego projektu. Otrzymasz połączenie logicznego i funkcjonalnego opakowania.
Potrzebne są otagowane przestrzenie nazw. Parser XML dla niektórych funkcji Jython może być oznaczony zarówno jako Jython, jak i XML, zamiast konieczności wybierania jednego lub drugiego.
A może wiję się.
źródło
Staram się projektować struktury pakietów w taki sposób, że gdybym miał narysować wykres zależności, byłby łatwy do naśladowania i używania spójnego wzorca, z jak najmniejszą liczbą odwołań cyklicznych.
Dla mnie jest to znacznie łatwiejsze do utrzymania i wizualizacji w systemie nazewnictwa pionowego niż poziomego. jeśli component1.display ma odniesienie do component2.dataaccess, powoduje to wyświetlenie większej liczby sygnałów ostrzegawczych, niż gdyby display.component1 miał odniesienie do dataaccess. komponent2.
Oczywiście komponenty wspólne dla obu są umieszczane we własnym pakiecie.
źródło
Całkowicie się zgadzam i proponuję logiczną („według funkcji”) organizację! Pakiet powinien być możliwie jak najbardziej zgodny z koncepcją „modułu”. Organizacja funkcjonalna może rozłożyć moduł na projekt, co skutkuje mniejszą hermetyzacją i podatnością na zmiany w szczegółach implementacji.
Weźmy na przykład wtyczkę Eclipse: umieszczenie wszystkich widoków lub akcji w jednym pakiecie byłoby bałaganem. Zamiast tego każdy składnik funkcji powinien trafiać do pakietu funkcji lub, jeśli jest ich wiele, do podpakietów (obsługi funkcji A., preferencji funkcji itp.)
Oczywiście problem tkwi w hierarchicznym systemie pakietów (który posiada m.in. Java), co sprawia, że obsługa ortogonalnych problemów jest niemożliwa lub co najmniej bardzo trudna - choć występują one wszędzie!
źródło
Osobiście wybrałbym nazewnictwo funkcjonalne. Krótki powód: unika powielania kodu lub koszmaru zależności.
Pozwól mi trochę rozwinąć. Co się dzieje, gdy używasz zewnętrznego pliku jar z własnym drzewem pakietów? Efektywnie importujesz (skompilowany) kod do swojego projektu, a wraz z nim (oddzielone funkcjonalnie) drzewo pakietów. Czy miałoby sens jednoczesne użycie obu konwencji nazewnictwa? Nie, chyba że to było przed tobą ukryte. I tak jest, jeśli twój projekt jest wystarczająco mały i składa się z jednego komponentu. Ale jeśli masz kilka jednostek logicznych, prawdopodobnie nie chcesz ponownie implementować, powiedzmy, modułu ładowania pliku danych. Chcesz dzielić go między jednostkami logicznymi, nie mieć sztucznych zależności między jednostkami niepowiązanymi logicznie i nie musisz wybierać, do której jednostki chcesz wstawić to konkretne narzędzie współdzielone.
Myślę, że właśnie dlatego nazewnictwo funkcjonalne jest najczęściej używane w projektach, które osiągają lub mają osiągnąć określony rozmiar, a nazewnictwo logiczne jest używane w konwencjach nazewnictwa klas w celu śledzenia określonej roli, jeśli którakolwiek z klas w pakiet.
Postaram się dokładniej odpowiedzieć na każdy z Twoich punktów dotyczących nazewnictwa logicznego.
Jeśli musisz łowić ryby w starych klasach, aby zmodyfikować funkcjonalności, gdy zmieniasz plany, jest to oznaka złej abstrakcji: powinieneś budować klasy, które zapewniają dobrze zdefiniowaną funkcjonalność, definiowalną jednym krótkim zdaniem. Tylko kilka klas najwyższego poziomu powinno zebrać wszystkie te elementy, aby odzwierciedlić analizę biznesową. W ten sposób będziesz mógł ponownie wykorzystać więcej kodu, mieć łatwiejszą konserwację, bardziej przejrzystą dokumentację i mniej problemów z zależnościami.
Zależy to głównie od sposobu, w jaki realizujesz swój projekt. Zdecydowanie widok logiczny i funkcjonalny jest ortogonalny. Więc jeśli używasz jednej konwencji nazewnictwa, musisz zastosować drugą do nazw klas, aby zachować pewien porządek, lub rozwidlić na jakiejś głębokości od jednej konwencji nazewnictwa do drugiej.
Modyfikatory dostępu to dobry sposób, aby umożliwić innym klasom, które rozumieją Twoje przetwarzanie, dostęp do wewnętrznych elementów Twojej klasy. Relacja logiczna nie oznacza zrozumienia ograniczeń algorytmicznych lub współbieżności. Funkcjonalne może, chociaż nie. Jestem bardzo zmęczony modyfikatorami dostępu innymi niż publiczne i prywatne, ponieważ często ukrywają one brak odpowiedniej architektury i abstrakcji klas.
W dużych, komercyjnych projektach zmiany technologii zdarzają się częściej, niż by się wydawało. Na przykład musiałem już 3 razy zmienić parser XML, 2 razy technologię buforowania i 2 razy oprogramowanie do geolokalizacji. Dobrze, że ukryłem wszystkie drobne szczegóły w dedykowanym opakowaniu ...
źródło
utils
pakietu. Wtedy wszystkie klasy, które tego potrzebują, mogą po prostu zależeć od tego pakietu.Interesującym eksperymentem jest całkowite nieużywanie pakietów (z wyjątkiem pakietu głównego).
Powstaje wówczas pytanie, kiedy i dlaczego warto wprowadzać pakiety. Można przypuszczać, że odpowiedź będzie inna niż ta, na którą odpowiedziałbyś na początku projektu.
Zakładam, że w ogóle pojawia się twoje pytanie, ponieważ pakiety są jak kategorie i czasami trudno jest zdecydować się na jedną lub drugą. Czasami tagi byłyby bardziej przydatne, aby przekazać, że klasa jest użyteczna w wielu kontekstach.
źródło
Z czysto praktycznego punktu widzenia konstrukcje widoczności języka Java umożliwiają klasom w tym samym pakiecie dostęp do metod i właściwości za pomocą
protected
idefault
widoczności, a takżepublic
tych. Używanie niepublicznych metod z zupełnie innej warstwy kodu z pewnością oznaczałoby duży zapach kodu. Dlatego staram się umieszczać klasy z tej samej warstwy w tym samym pakiecie.Rzadko używam tych chronionych lub domyślnych metod gdzie indziej - z wyjątkiem być może w testach jednostkowych dla klasy - ale kiedy to robię, zawsze jest to z klasy w tej samej warstwie
źródło
To zależy. W mojej pracy czasami dzielimy pakiety według funkcji (dostęp do danych, analityka) lub według klas aktywów (kredyt, akcje, stopy procentowe). Po prostu wybierz strukturę, która jest najwygodniejsza dla Twojego zespołu.
źródło
Z mojego doświadczenia wynika, że ponowne użycie stwarza więcej problemów niż ich rozwiązywanie. W przypadku najnowszych i tanich procesorów i pamięci wolałbym raczej kopiować kod niż ściśle integrować go w celu ponownego wykorzystania.
źródło