Question

I like domain driven design, and onion design. However I would really like my features to be encapsulated. If i check out some legacy code and I need to investigate some specific feature the best thing that I think could happen to me is that I see (lets say in java) is that there would be a package with featurename and all the feature functionality would reside in that package. In a way that if i delete the package the feature would be gone.

While I understand its impossible because some of the feature might reside in other service I'm trying to at least think if its correct to do that in my own service.

My problem with this is that usually in DDD and in onion design you have the domain which contains a bunch of domain objects. now some of these objects are of "my feature" and some of them are of other features. lets say (user authentication is one feature and user twitter account details is another feature).

Is there any design methodology that does something with it? is what I'm suggesting makes sense?

Example: you have a project that manages users waiting in line. You are asked to add a limit, max on the queue size. you need to read that max queue size from configuration. Then in your logic you need to apply that max queue size. You also need to expose this configuration of max queue size in jmx, also you have an api to that service so you need to expose that max queue size as an api in that service.

Current app packages: --> conf (deals with reading configuration) // do change here. --> domain // do change here for that small subfeature. --> logic // do change also here for that small subfeature --> service (where api are) // do also a change here.

so in the above simplistic example for a simple subfeature addition i need to make changes to at least 4 packages. Which means my subfeature is spread among these packages and classes.

I understand DDD mentions boundcontext but notice my feature here is very very small! just adding a max limitation on size. So it does not seem appropriate to open a boundedcontext on each such small subfeature.

So the way I deal with it today I go conf i find there a class named Configuration which contains various configurations. I add to that Configuration class a member named Configuration.maxQueueSize.

I go then to domain to model that new property I have there lets say Account I add to the Account class Account.maxQueueSize.

I go then to service an expose a new method named getMaxQueueSize

as you see I went to multiple packages and multiple classes already dealing with other functionality and extended them.

This is a very small feature, for one hand I cant imaging that for every small feauter I would open a whole new bounded context with its own service, domain, conf packages (and lets assume there are more) so that each feature is in its own package.

On the other way on each feature I need to extend configuration, domain, service, ... which means no single feature is encapsulated well enough so that I can just delete it or see a package which its fully resides in so that I can better understand the app.

Was it helpful?

Solution

If I have understood correctly, your 4 packages (conf, domain, logic and service) are layers in your application. These layers do not separate different "functionality" as you call it, but different concerns. As such, it is perfectly fine that you have to touch all 4 of those packages when adding a new function however small it may be.

If you would have to add a new feature which is unrelated to your current "queue management" feature, you could add a new feature package which contains all 4 of your layers, but the "max queue size" would belong in your current feature, I'd say.

OTHER TIPS

What you are looking for is called Bounded Context in DDD.

Now, as you are realizing in your question, bounded contexts might share data. The best way to implement it that I saw is simply to duplicate the entities. So Accounting context has Customer, and so does Production have Customer. But even though they have same name, they have completely different meaning. It then becomes responsibility of the layer above (most probably infrastructure layer) to save and load them into same table. But from the point of individual contexts, the customers are completely separate. This then allows you to completely remove any bounded context from the whole application without affecting any other part.

As it relates to Onion Architecture, I would design each "feature module" as a "slice" through the onion where each layer is single library. Imagine cutting out a piece round cake. So you could have libraries Feature1.Domain, Feature1.Database and Feature2.Domain, Feature2.Database, etc.. between which would be relationships according onion architecture.

But there is a problem. As you might know, some modules might want to communicate between each other. That's where Bridging libraries come in. Simply said, you could have library Feature1Feature2Bridge. This library would rely on Feature1.Domain and Feature2.Domain providing abstractions for communication, and this "Bridge" library would implement that communication. Then, this library would be loaded only if both features are loaded.

When I find that all the changes I'm asked to make require tiny changes to several different modules, that usually means that the way I've split the project into modules is inappropriate.

To give an example which I believe is similar to yours, one of the services I work on converts documents from an old format to a new format. Whenever a new feature becomes supported in the new format, I have to go back into this conversion service and:

  • Add the parameters representing this feature to the model objects for old and new documents.
  • Update the logic for fetching the old document from the old database, so it gets the old version of the feature.
  • Add logic to convert the old version of the feature into the new version.
  • Update a sort-of-config file to say that the service is now using model version 1.11 instead of version 1.10.

In my case, all of this is a single commit to a single repository that's deployed as a single package, simply because that's how we chose to split up our packages. Yes, it's still (roughly) four separate files, but I don't believe that's a problem as long as that set of changes can be conveniently committed, reviewed, deployed, etc as a single cohesive unit (which it can if you have decent tools).

For instance, it sounds like your model objects are in a separate package from the logic that operates on them, which is probably unnecessary unless said objects have to be shared among many other services. That's even more true for your configuration file, as I can't imagine that being reused by other services (unless the configuration is horridly overcomplicated). It's also difficult to think of any scenario where having a service's API and its logic in two separate packages would be a good idea. I can think of some derivative products like API documentation which perhaps belong in separate packages (though ideally those would be autogenerated as part of your build/deploy process), but the actual API/logic seem like they should normally go together.

Licensed under: CC-BY-SA with attribution
scroll top