Recently in my packwerk journey, I’ve been wondering how best to handle dependencies in packages that seem to deal with every other part of the app.
For example, I have a premium package with objects that are responsible for enabling certain features across the app. Because of this, the package has a lot of dependencies and in the future I imagine this will grow whenever a new package wants to enable a premium feature.
I saw on Gitlab, they had a similar situation dealing with an Audit package. Many parts of the app would define events that could be audited and that resulted in their package growing to include all of these dependencies:
Following their discussion, they proposed moving the domain specific pieces into the packages for that domain and out of the Audit package. The dependency graph gets inverted and that way, the Audit package doesn’t churn continually.
I’ve also come to a similar conclusion where if my other components want to enable premium features then they can specify a dependency on the premium component and use the services it provides to create their own premium rules.
Since I’ve seen this pattern with several domains in my codebase as well as other codebases that I follow, I wanted to see if anyone else had similar experiences and what their solutions were. How did you model the dependencies and did it work well for you?
It is not immediately obvious to me why this package would depend on many other packages to activate premium features. It sounds to me like we should expect to see many other packages depending on it. But that’s me analyzing without seeing the actual contents…
What you write further down in the post has me thinking that “the features” in here and that, in general seems like an anti pattern. In my book, I discuss the evolution of this kind of problem in the chapter on dependency violations, specifically the section on dependency injection.
The specificity I would like to add here is to think through which piece you need in your situation:
a premium feature: that is a part of the domain that it makes “premium”
premium ability: shared language (i.e., interfaces) that is specific to questions of whether things are premium, how they become premium, etc. These might include method interfaces where the implementation would hold state, in which case…
premium status service: The server/service that know about the current state of premiumness in the system. If you create the interfaces described above, packages don’t need to be dependent on this directly because this can be dependency injected.
I think your blind analysis is spot on and I agree with it. It is interesting though that it is easy to fall into this type of design though since I see it in a lot of places. I’ll check out the chapter you mention, I am eager to read more about it!
This is a good way to think about it, thank you!
I am curious to hear of other situations people have encountered where they weren’t sure which way the dependency ran. Did you experience anything like this?