Java 프로젝트에서 패키지 이름 지정에 어떤 전략을 사용하며 그 이유는 무엇입니까? [닫은]
나는 이것에 대해 얼마 전에 생각했고, 내 가게가 첫 번째 실제 자바 웹 앱을 만들고 있기 때문에 최근에 다시 떠올랐다.
소개로 두 가지 주요 패키지 이름 지정 전략을 봅니다. (명확하게 말하면,이 부분의 전체 'domain.company.project'부분을 말하는 것이 아니라 그 아래에있는 패키지 규칙에 대해 이야기하고 있습니다.) 어쨌든 내가 보는 패키지 명명 규칙은 다음과 같습니다.
기능적 : 비즈니스 도메인에 따른 ID가 아닌 구조적으로 기능에 따라 패키지 이름을 지정합니다. 이것에 대한 또 다른 용어는 '레이어'에 따른 이름 지정일 수 있습니다. 따라서 * .ui 패키지와 * .domain 패키지 및 * .orm 패키지가 있습니다. 패키지는 수직이 아닌 수평 슬라이스입니다.
이것은 논리적 이름 지정보다 훨씬 일반적입니다. 사실 저는 이런 일을하는 프로젝트를 본 적이 없거나 들어 본 적이 없다고 생각합니다. 물론 이것은 내가 끔찍하게 똑똑하지 않고 모든 사람들이 그들이하는 방식으로 그것을 할 큰 이유가 있다고 생각하기 때문에 (당신이 NP 문제에 대한 해결책을 생각해 냈다고 생각하는 것과 비슷하게) 나를 걱정하게 만듭니다. 반면에, 난 그냥 방에있는 코끼리 실종자에 반대 아니에요 그리고 나는 실제 인수 들어 본 적이 에 대한 이런 식의 이름을 지정할 패키지를하고. 사실상 표준 인 것 같습니다.
논리적 : 비즈니스 도메인 ID에 따라 패키지 이름을 지정 하고 기능의 수직적 조각과 관련된 모든 클래스를 해당 패키지에 넣습니다.
나는 전에 언급했듯이 이것을 보거나 들어 본 적이 없지만 나에게는 의미가 있습니다.
저는 시스템에 수평이 아닌 수직으로 접근하는 경향이 있습니다. 데이터 액세스 계층이 아닌 주문 처리 시스템에 들어가서 개발하고 싶습니다. 당연히 그 시스템을 개발할 때 데이터 액세스 계층을 건드릴 가능성이 있지만 요점은 그렇게 생각하지 않는다는 것입니다. 물론 이것이 의미하는 바는 변경 명령을 받거나 새로운 기능을 구현하고 싶을 때 관련된 모든 클래스를 찾기 위해 여러 패키지에서 낚시를하지 않아도된다는 것입니다. 대신 X 패키지를 살펴 봅니다. 제가하는 일은 X와 관련이 있기 때문입니다.
개발 관점에서 볼 때 패키지가 아키텍처가 아닌 비즈니스 도메인을 문서화하는 것이 큰 승리라고 생각합니다. 나는 도메인은 거의 항상 시스템의 아키텍처, 특히이 시점에서 구현에서 거의 평범 해지는 곳에서 찾기 어려운 시스템의 일부라고 느낍니다. 이런 유형의 명명 규칙을 사용하고 패키지 명명에서 즉시 시스템에 도달 할 수 있다는 사실은 주문, 고객, 기업, 제품 등을 처리한다는 것을 알기 때문에 매우 편리해 보입니다.
이렇게하면 Java의 액세스 수정자를 훨씬 더 잘 활용할 수있을 것 같습니다. 이를 통해 시스템의 계층이 아닌 하위 시스템에 인터페이스를 훨씬 더 명확하게 정의 할 수 있습니다. 따라서 투명하게 지속되기를 원하는 주문 하위 시스템이있는 경우 이론적으로는 dao 계층에서 지속성 클래스에 대한 공용 인터페이스를 만들지 않고 대신 dao 클래스를 그것이 다루는 클래스들과 함께. 물론, 경우에 이 기능을 노출하고 싶어, 당신은 그것을위한 인터페이스를 제공하거나 공개 할 수 있습니다. 시스템 기능의 수직 슬라이스를 여러 패키지로 분할하여 많은 것을 잃어버린 것처럼 보입니다.
내가 볼 수있는 한 가지 단점은 레이어를 찢어내는 것이 조금 더 어렵다는 것입니다. 패키지를 삭제하거나 이름을 변경 한 다음 대체 기술을 사용하여 새 패키지를 삭제하는 대신 모든 패키지에있는 모든 클래스를 변경해야합니다. 그러나 나는 이것이 큰 문제라고 생각하지 않습니다. 경험 부족 때문일 수도 있지만, 시스템 내에서 수직 기능 슬라이스를 편집하고 들어가는 횟수에 비해 기술을 교체하는 횟수가 적다는 것을 상상해야합니다.
그래서 질문이 당신에게 나올 것이라고 생각합니다. 당신은 어떻게 당신의 패키지 이름을 짓고 왜 그럴까요? 내가 여기서 황금 거위 같은 것을 우연히 만났다고 생각하지는 않는다는 것을 이해하십시오. 나는 대부분 학문적 경험을 가진이 모든 것에 꽤 익숙합니다. 그러나 나는 내 추론의 허점을 발견 할 수 없으므로 여러분 모두가 앞으로 나아갈 수 있기를 바랍니다.
패키지 디자인의 경우 먼저 레이어로 나눈 다음 다른 기능으로 나눕니다.
몇 가지 추가 규칙이 있습니다.
- 레이어는 가장 일반적인 (하단)에서 가장 구체적인 (상단)까지 쌓입니다.
- 각 레이어에는 공용 인터페이스 (추상화)가 있습니다.
- 레이어는 다른 레이어의 공용 인터페이스에만 의존 할 수 있습니다 (캡슐화).
- 레이어는보다 일반적인 레이어에만 의존 할 수 있습니다 (위에서 아래로의 종속성).
- 레이어는 바람직하게는 바로 아래에있는 레이어에 의존합니다.
예를 들어 웹 애플리케이션의 경우 애플리케이션 계층에 다음과 같은 계층이있을 수 있습니다 (위에서 아래로).
- 프레젠테이션 레이어 : 클라이언트 계층에 표시 될 UI를 생성합니다.
- 응용 프로그램 계층 : 응용 프로그램에 고유 한 논리 포함, 상태 저장
- 서비스 계층 : 도메인별로 기능 그룹화, 상태 비 저장
- 통합 계층 : 백엔드 계층 (db, jms, 이메일 등)에 대한 액세스를 제공합니다.
결과 패키지 레이아웃의 경우 다음과 같은 몇 가지 추가 규칙이 있습니다.
- 모든 패키지 이름의 루트는
<prefix.company>.<appname>.<layer>
- 레이어의 인터페이스는 기능별로 더 나뉩니다.
<root>.<logic>
- 레이어의 개인 구현은 private로 시작됩니다.
<root>.private
다음은 레이아웃의 예입니다.
프리젠 테이션 계층은보기 기술과 선택적으로 애플리케이션 그룹으로 나뉩니다.
com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...
애플리케이션 계층은 사용 사례로 나뉩니다.
com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...
The service layer is divided into business domains, influenced by the domain logic in a backend tier.
com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...
The integration layer is divided into 'technologies' and access objects.
com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...
Advantages of separating packages like this is that it is easier to manage complexity, and it increases testability and reusability. While it seems like a lot of overhead, in my experience it actually comes very natural and everyone working on this structure (or similar) picks it up in a matter of days.
Why do I think the vertical approach is not so good?
In the layered model, several different high-level modules can use the same lower-level module. For example: you can build multiple views for the same application, multiple applications can use the same service, multiple services can use the same gateway. The trick here is that when moving through the layers, the level of functionality changes. Modules in more specific layers don't map 1-1 on modules from the more general layer, because the levels of functionality they express don't map 1-1.
When you use the vertical approach for package design, i.e. you divide by functionality first, then you force all building blocks with different levels of functionality into the same 'functionality jacket'. You might design your general modules for the more specific one. But this violates the important principle that the more general layer should not know about more specific layers. The service layer for example shouldn't be modeled after concepts from the application layer.
I find myself sticking with Uncle Bob's package design principles. In short, classes which are to be reused together and changed together (for the same reason, e.g. a dependency change or a framework change) should be put in the same package. IMO, the functional breakdown would have better chance of achieving these goals than the vertical/business-specific break-down in most applications.
For example, a horizontal slice of domain objects can be reused by different kinds of front-ends or even applications and a horizontal slice of the web front-end is likely to change together when the underlying web framework needs to be changed. On the other hand, it's easy to imagine the ripple effect of these changes across many packages if classes across different functional areas are grouped in those packages.
Obviously, not all kinds of software are the same and the vertical breakdown may make sense (in terms of achieving the goals of reusability and closeability-to-change) in certain projects.
There are usually both levels of division present. From the top, there are deployment units. These are named 'logically' (in your terms, think Eclipse features). Inside deployment unit, you have functional division of packages (think Eclipse plugins).
For example, feature is com.feature
, and it consists of com.feature.client
, com.feature.core
and com.feature.ui
plugins. Inside plugins, I have very little division to other packages, although that's not unusual too.
Update: Btw, there is great talk by Juergen Hoeller about code organization at InfoQ: http://www.infoq.com/presentations/code-organization-large-projects. Juergen is one of architects of Spring, and knows a lot about this stuff.
Most java projects I've worked on slice the java packages functionally first, then logically.
Usually parts are sufficiently large that they're broken up into separate build artifacts, where you might put core functionality into one jar, apis into another, web frontend stuff into a warfile, etc.
Packages are to be compiled and distributed as a unit. When considering what classes belong in a package, one of the key criteria is its dependencies. What other packages (including third-party libraries) does this class depend on. A well-organized system will cluster classes with similar dependencies in a package. This limits the impact of a change in one library, since only a few well-defined packages will depend on it.
It sounds like your logical, vertical system might tend to "smear" dependencies across most packages. That is, if every feature is packaged as a vertical slice, every package will depend on every third party library that you use. Any change to a library is likely to ripple through your whole system.
I personally prefer grouping classes logically then within that include a subpackage for each functional participation.
Goals of packaging
Packages are after all about grouping things together - the idea being related classes live close to each other. If they live in the same package they can take advantage of package private to limit visibility. The problem is lumping all your view and persitance stuff into one package can lead to a lot of classes being mixed up into a single package. The next sensible thing to do is thus create view, persistance, util sub packages and refactor classes accordingly. Underfortunately protected and package private scoping does not support the concept of the current package and sub package as this would aide in enforcing such visibility rules.
이제 모든 뷰 관련 항목을 그룹화하기 위해 어떤 값이 있는지 기능을 통해 분리 된 값을 볼 수 있습니다. 이 이름 지정 전략의 항목은 뷰의 일부 클래스와 연결이 끊어지고 다른 클래스는 지속성에 있습니다.
내 논리적 패키징 구조의 예
설명을 위해 두 모듈의 이름을 지정합니다. 패키지 트리의 특정 분기 아래에 클래스를 그룹화하는 개념으로 모듈 이름을 사용합니다.
apple.model apple.store banana.model banana.store장점
Banana.store.BananaStore를 사용하는 클라이언트는 우리가 제공하고자하는 기능에만 노출됩니다. 최대 절전 버전은 인식 할 필요가 없으며 스토리지 작업에 혼란을 추가 할 때 이러한 클래스를 볼 필요가없는 구현 세부 사항입니다.
기타 논리적 v 기능적 이점
The further up towards the root the broader the scope becomes and things belonging to one package start to exhibit more and more dependencies on things belonging to toher modules. If one were to examine for example the "banana" module most of the dependencies would be limited to within that module. In fact most helpers under "banana" would not be referenced at all outside this package scope.
Why functionality ?
What value does one achieve by lumping things based on functionality. Most classes in such a case are independent of each other with little or no need to take advantage of package private methods or classes. Refactoring them so into their own subpackages gains little but does help reduce the clutter.
Developer changes to the system
When developers are tasked to make changes that are a bit more than trivial it seems silly that potentially they have changes that include files from all areas of the package tree. With the logical structured approach their changes are more local within the same part of the package tree which just seems right.
Both functional (architectural) and logical (feature) approaches to packaging have a place. Many example applications (those found in text books etc.) follow the functional approach of placing presentation, business services, data mapping, and other architectural layers into separate packages. In example applications, each package often has only a few or just one class.
This initial approach is fine since a contrived example often serves to: 1) conceptually map out the architecture of the framework being presented, 2) is done so with a single logical purpose (e.g. add/remove/update/delete pets from a clinic). The problem is that many readers take this as a standard that has no bounds.
As a "business" application expands to include more and more features, following the functional approach becomes a burden. Although I know where to look for types based on architecture layer (e.g. web controllers under a "web" or "ui" package, etc.), developing a single logical feature begins to require jumping back and forth between many packages. This is cumbersome, at the very least, but its worse than that.
Since logically related types are not packaged together, the API is overly publicized; the interaction between logically related types is forced to be 'public' so that types can import and interact with each other (the ability to minimize to default/package visibility is lost).
If I am building a framework library, by all means my packages will follow a functional/architectural packaging approach. My API consumers might even appreciate that their import statements contain intuitive package named after the architecture.
Conversely, when building a business application I will package by feature. I have no problem placing Widget, WidgetService, and WidgetController all in the same "com.myorg.widget." package and then taking advantage of default visibility (and having fewer import statements as well as inter-package dependencies).
There are, however, cross-over cases. If my WidgetService is used by many logical domains (features), I might create a "com.myorg.common.service." package. There is also a good chance that I create classes with intention to be re-usable across features and end up with packages such as "com.myorg.common.ui.helpers." and "com.myorg.common.util.". I may even end up moving all these later "common" classes in a separate project and include them in my business application as a myorg-commons.jar dependency.
It depends on the granularity of your logical processes?
If they're standalone, you often have a new project for them in source control, rather than a new package.
The project I'm on at the moment is erring towards logical splitting, there's a package for the jython aspect, a package for a rule engine, packages for foo, bar, binglewozzle, etc. I'm looking at having the XML specific parsers/writers for each module within that package, rather than having an XML package (which I have done previously), although there will still be a core XML package where shared logic goes. One reason for this however is that it may be extensible (plugins) and thus each plugin will need to also define its XML (or database, etc) code, so centralising this could introduce problems later on.
In the end it seems to be how it seems most sensible for the particular project. I think it's easy to package along the lines of the typical project layered diagram however. You'll end up with a mix of logical and functional packaging.
What's needed is tagged namespaces. An XML parser for some Jython functionality could be tagged both Jython and XML, rather than having to choose one or the other.
Or maybe I'm wibbling.
I try to design package structures in such a way that if I were to draw a dependency graph, it would be easy to follow and use a consistent pattern, with as few circular references as possible.
For me, this is much easier to maintain and visualize in a vertical naming system rather than horizontal. if component1.display has a reference to component2.dataaccess, that throws off more warning bells than if display.component1 has a reference to dataaccess. component2.
Of course, components shared by both go in their own package.
I totally follow and propose the logical ("by-feature") organization! A package should follow the concept of a "module" as closely as possible. The functional organization may spread a module over a project, resulting in less encapsulation, and prone to changes in implementation details.
Let's take an Eclipse plugin for example: putting all the views or actions in one package would be a mess. Instead, each component of a feature should go to the feature's package, or if there are many, into subpackages (featureA.handlers, featureA.preferences etc.)
Of course, the problem lies in the hierarchical package system (which among others Java has), which makes the handling of orthogonal concerns impossible or at least very difficult - although they occur everywhere!
I would personally go for functional naming. The short reason: it avoids code duplication or dependency nightmare.
Let me elaborate a bit. What happens when you are using an external jar file, with its own package tree? You are effectively importing the (compiled) code into your project, and with it a (functionally separated) package tree. Would it make sense to use the two naming conventions at the same time? No, unless that was hidden from you. And it is, if your project is small enough and has a single component. But if you have several logical units, you probably don't want to re-implement, let's say, the data file loading module. You want to share it between logical units, not have artificial dependencies between logically unrelated units, and not have to choose which unit you are going to put that particular shared tool into.
I guess this is why functional naming is the most used in projects that reach, or are meant to reach, a certain size, and logical naming is used in class naming conventions to keep track of the specific role, if any of each class in a package.
I will try to respond more precisely to each of your points on logical naming.
If you have to go fishing in old classes to modify functionalities when you have a change of plans, it's a sign of bad abstraction: you should build classes that provide a well defined functionality, definable in one short sentence. Only a few, top-level classes should assemble all these to reflect your business intelligence. This way, you will be able to reuse more code, have easier maintenance, clearer documentation and less dependency issues.
That mainly depends on the way you grok your project. Definitely, logical and functional view are orthogonal. So if you use one naming convention, you need to apply the other one to class names in order to keep some order, or fork from one naming convention to an other at some depth.
Access modifiers are a good way to allow other classes that understand your processing to access the innards of your class. Logical relationship does not mean an understanding of algorithmic or concurrency constraints. Functional may, although it does not. I am very weary of access modifiers other than public and private, because they often hide a lack of proper architecturing and class abstraction.
In big, commercial projects, changing technologies happens more often than you would believe. For instance, I have had to change 3 times already of XML parser, 2 times of caching technology, and 2 times of geolocalisation software. Good thing I had hid all the gritty details in a dedicated package...
It is an interesting experiment not to use packages at all (except for the root package.)
The question that arises then, is, when and why it makes sense to introduce packages. Presumably, the answer will be different from what you would have answered at the beginning of the project.
I presume that your question arises at all, because packages are like categories and it's sometimes hard to decide for one or the other. Sometimes tags would be more appreciate to communicate that a class is usable in many contexts.
From a purely practical standpoint, java's visibility constructs allow classes in the same package to access methods and properties with protected
and default
visibility, as well as the public
ones. Using non-public methods from a completely different layer of the code would definitely be a big code smell. So I tend to put classes from the same layer into the same package.
I don't often use these protected or default methods elsewhere - except possibly in the unit tests for the class - but when I do, it is always from a class at the same layer
It depends. In my line of work, we sometimes split packages by functions (data access, analytics) or by asset class (credit, equities, interest rates). Just select the structure which is most convenient for your team.
From my experience, re-usability creates more problems than solving. With the latest & cheap processors and memory, I would prefer duplication of code rather than tightly integrating in order to reuse.
'programing' 카테고리의 다른 글
`= default` 이동 생성자는 멤버 단위 이동 생성자와 동일합니까? (0) | 2020.09.10 |
---|---|
setter가없는 속성이 직렬화되지 않는 이유 (0) | 2020.09.10 |
매개 변수와 함께 함수에 대한 참조를 어떻게 전달할 수 있습니까? (0) | 2020.09.10 |
MySQL의 ORDER BY RAND () 함수를 어떻게 최적화 할 수 있습니까? (0) | 2020.09.10 |
비 정적 데이터 멤버 및 중첩 클래스 생성자의 클래스 내 초기화를 사용할 때 오류 발생 (0) | 2020.09.10 |