1

Consider the following simple package structure:

com.app.parser
  - IParser
com.app.apples
  - Apple
com.app.bananas
  - Banana

Where should I put my AppleParser/BananaParser?

Into the com.app.parser package or in a more domain focused package, e.g. AppleParser into com.app.apples.tools?

So far, I tried to package my classes by features as suggested by e.g. Medium or Martin Fowler, but what defines that feature? In the example above, is the feature about the parsing or about the "domain" like apple/banana?

andrewJames
  • 19,570
  • 8
  • 19
  • 51
cobby
  • 484
  • 3
  • 18
  • Where do you want the knowledge? Do you want anything in com.app.parser.* to know about Apples or Bananas? Or is it ok for something in com.app.apples to know how to provide parsing capabilities? – Fildor Nov 16 '22 at 11:25
  • I don't really know. Could you explain that? Why does it influence the decision from a pragmatic point of view? – cobby Nov 16 '22 at 11:34
  • 2
    What's the point of your com.app.parser package? Is it to provide a _clean and uniform API_ to all parsing in your system? Then you wouldn't spoil it with implementation details from specific domain packages, right? You would want to keep it generic. And then: what's the point of the com.app.apples package? It's to provide all functionality about apples, right? So it makes sense for it to contain an IParser implementing AppleParser that parses strings to Apples. – Fildor Nov 16 '22 at 11:40
  • You are correct. The idea behind adding an AppleParser to com.app.apples is to have all the "apple" related code in one place, making it easy to remove the "apple" feature or use it elsewhere. However, one could also make a case for including it in the com.app.parser package. I don't think there is a best solution, but rather a judgment call. – cobby Nov 16 '22 at 11:47

1 Answers1

1

To frame the question a bit, there are two broad strategies for packaging classes.

  1. Package by function (horizontal slicing).
  2. Package by feature (vertical slicing).

The first strategy would result in something like: a package for controllers, a package for services, and a package for repositories. The second strategy would result in something like: a controller, a service, and a repository per package.

In my experience, the first strategy is more common; but it has drawbacks.

  1. Nearly all classes in the application must be public, because they are called from a different package; and nearly all classes call out to another package as well.
  2. Nearly all classes are packaged together with other classes they do not interact with.

In other words, the package-by-function strategy results in high coupling between packages and low cohesion within packages. On the flip side, the package-by-feature strategy is by no means flawless. In particular, it assumes little or no code reuse between features.


When writing application code in Java, I have two guidelines for packaging classes.

  1. Minimize the number of classes per package.
  2. Minimize the number of public classes.

These are opposing guidelines, because every package requires at least one public class as its entry point. So the difficulty is in striking a balance between the two. In a very general sense, you may consider the first guideline to focus more on package-by-function and the second guideline to focus more on package-by-feature; so the two guidelines together aim to balance the two strategies.

Their commonality is that both guidelines aim to reduce complexity. All else being equal, large packages are more complex than small packages; and broadly-scoped code is more complex than narrowly-scoped code. So I strive for small packages containing mostly default (package-private) classes.


In terms of the parser in question,

what defines that feature?

The user. Features are defined by users. When a user interacts with your application, e.g. by calling an API, all the code executed to provide the response to that call comprises a feature.

is the feature about the parsing or about the "domain" like apple/banana?

Neither. The feature is about what the user wants to do. A feature may include parsing of apples or bananas or both, but also needs to accept the user's request parameters, retrieve the apples and bananas from storage, select the parser, format the output, etc., etc.

I would want to: a.) put the parser in a package that allows as many of its feature's classes to be non-public as possible, while b.) limiting the size of the package itself.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • Good point about the package privacy. So you wouldn't consider system requirements like e.g. networking a feature? – cobby Nov 17 '22 at 07:26
  • I also think features and functionality are not contradicting, e.g. I could create a common "functional" package like com.app.parser or com.app.shared with the interface IParser and add the implementation to the "feature" packages, e.g. com.app.AppleParser. This leads to high cohesion within the feature, but still promotes code reusage, right? Slicing packages by features should be default when you think about software as a means to end to fulfill user requirements. Makes it easy to remove/change/add requirements. – cobby Nov 17 '22 at 07:49
  • Interface reuse and implementation reuse may be considered as separate abilities. It is not unusual for multiple features to share one implementation of some interface. Also consider the purpose of an interface... if each feature exclusively uses one unique implementation, [why create an interface at all](https://stackoverflow.com/q/2659366/1371329)? In this case each parser is a private utility, not a shared service. – jaco0646 Nov 30 '22 at 18:34