To frame the question a bit, there are two broad strategies for packaging classes.
- Package by function (horizontal slicing).
- 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.
- 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.
- 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.
- Minimize the number of classes per package.
- 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.