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.
3 Answers 3
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.
-
I didn't understand the second section starting with "if you would have to add.."Jas– Jas2016年02月15日 12:42:24 +00:00Commented Feb 15, 2016 at 12:42
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.
-
1I may be mistaken, but I think OP is referring to having features that apply to the project if present and don't apply to the project if not present, without creating problems in compilation. Please correct me if I'm wrong, OP.Neil– Neil2015年12月21日 08:17:31 +00:00Commented Dec 21, 2015 at 8:17
-
@Neil What do you mean exactly? In that case, his question has nothing to do with DDD or Onion Architecture.Euphoric– Euphoric2015年12月21日 08:21:05 +00:00Commented Dec 21, 2015 at 8:21
-
I mean, this answer seems a little vague in the context of the question. Could you clarify with a tiny example perhaps?Neil– Neil2015年12月21日 08:23:33 +00:00Commented Dec 21, 2015 at 8:23
-
@Neil The answer is vague because the question is vague. This is about high-level design. I can't really go into any detail unless question provides more detail of it's own.Euphoric– Euphoric2015年12月21日 08:46:01 +00:00Commented Dec 21, 2015 at 8:46
-
this is good for big feature but I was asking in example about small
subfeatures
with them I see myself changing multiple places.Jas– Jas2015年12月21日 14:39:08 +00:00Commented Dec 21, 2015 at 14:39
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.
-
The "One commit per feature" was tried and found to be inadequate. For example, how do you handle subsequent changes to the feature after it is committed?Euphoric– Euphoric2016年02月13日 20:21:10 +00:00Commented Feb 13, 2016 at 20:21
-
@Euphoric I assumed this question was specifically about the initial implementation of a feature, without worrying about any subsequent iterations. Of course you have to do multiple commits if you do multiple iterations. That's separate from the question of whether each iteration should be split into several commits solely because it involves version number and config files as well as business logic. Or in slogan form: I'm arguing for "one commit per change" rather than "one commit per feature".Ixrec– Ixrec2016年02月13日 20:25:36 +00:00Commented Feb 13, 2016 at 20:25
-
Then your answer is not valid, because question is exactly about "separation of features" not "changes".Euphoric– Euphoric2016年02月13日 20:27:43 +00:00Commented Feb 13, 2016 at 20:27
-
@Euphoric The question is about a change that takes four commits to implement despite all being for the same iteration of a feature and none of them making sense independently of each other. I agree that neither I nor the OP have been especially picky about our use of terms like "feature", but I don't think that terminology issue is relevant to the question or the validity of either answer. Nor am I particularly interested in it. If this still bothers you it's probably better to ask the OP what he wants or suggest some edits to correct our usage.Ixrec– Ixrec2016年02月13日 20:32:40 +00:00Commented Feb 13, 2016 at 20:32
Explore related questions
See similar questions with these tags.