views:

599

answers:

3

With the latest additions to our dependency injection framework (annotations in spring), the marginal cost of creating DI-managed components seems to have hit some critical new threshold. While there previously was an overhead associated with spring (tons of XML and additional indirections), dependency injection seems to have started going where lots of patterns go; they go under the hood and "disappear".

The consequence of this is that the conceptual overhead associated with a large number of components becomes acceptable. It's arguable that we could make a system where most classes only expose one single public method and build the whole system by just aggregating these pieces like crazy. In our case a few things are given; the user interface of your application has some functional requirements that shape the topmost services. And the back-end systems control the lower part. But in between these two, everything is up for grabs.

Our constant discussion is really why are we grouping things in classes and what should the principles be ? A couple of things are certain; the facade pattern is dead and buried. Any service containing multiple unrelated features also tend to get split up. "Unrelated feature" is interpreted in an extremely much stricter sense than I have ever done earlier.

In our team there are two prevailing trains of thought here: Implementation dependencies restrict grouping; any functionality in a single class should preferably be a client of all injected dependencies. We are a DDD project and the other fraction thinks the domain restricts grouping (CustomerService or finer grained CustomerProductService, CustomerOrderService) - normalized usage of injected dependencies is unimportant.

So in the loosely coupled DI universe, why are we grouping logic in classes ?

edit: duffymo point out that this may be moving towards a functional style of programming; which brings up the issue of state. We have quite a few "State" objects that represent (small) pieces of relevant application state. We inject these into any service that has a legitimate need for this state. (The reason we use "State" objects instead of regular domain objects is that spring construct these at an unspecified time. I see this as a slight workaround or alternate solution to letting spring manage the actual creation of domain objects. There may be better solutions here).

So for instance any service that needs OrderSystemAccessControlState can just inject this, and the scope of this data is not readily known to the consumer. Some of the security-relate state is typically used at a lot of different levels but totally invisible on the levels in-between. I really think this violates fundamentally with functional principles. I even had a hard time adjusting to this concept form an OO perspective - but as long as the injected state is precise and strongly type then the need is legit aka the use case is proper.

+1  A: 

Pure DI perfect universe, I think single classes+method design is ideal. In reality we need to balance the cost of that which makes it less feasible.

Cost factors

  • Overhead of DI. Spinning up all the underlying and related underlyings for a single method is expensive. Grouping into a class allows us to offset some of that.
  • Skills - DI is new to many (myself ESPECIALLY) so understanding how to do it better or get out of old/habitual designs is tough
  • Brown field apps which have them already, it's easier/cheaper/quicker to live with them and worry about this in future green field apps

Hopefully my newbie-ness (yes, I am filled with made up words) with DI hasn't made me completely wrong with this.

Robert MacLean
At least with spring the wiring overhead is mostly a constant factor happening at app startup. Run-time wiring overhead is minimal. SO I really think the DI overhead is not an issue. The conceptual issues are much more important.
krosenvold
When we interact with a legacy interface we can still choose to create many micro-components right on top of this interface. Arguably you could also do this a lot of places inside a legacy application, taking out one method at a time from a legacy impl, could be a smart way to evolve a legacy app..
krosenvold
@ krosenvold: It depends what you use the DI container for. If you use it to create a few services there's no problem. If you use it to create large amounts of domain objects the overhead is certainly a problem.
Mendelt
@Mendelt Accepted. We don't DI domain objects.
krosenvold
Exactly. For me, domain objects are created in service method scope based on the input parameters. They aren't injected. The question is starting to sound like "Is this model moving me towards a functional approach to development and away from objects?" to me.
duffymo
Well not really. I choose exactly where to put my state. Small services, small chunks of state. I just put state wherever it feels correct.
krosenvold
+2  A: 

I can think of two reasons.

Maintainability: You'll naturally expect some logic to go together. Logic that defines operations on one particular outside service for example a database should probably be grouped together in a logical way. You can do this in a namespace or a class.

State and identity: objects do not only contain logic but also maintain state. Logic that is part of the interface of working with the state of a particular object should be defined on that object. Objects also maintain identity, an object that models one entity in the problem domain should be one object in your software.

As a side-node: The state and identity argument is mostly applicable to domain objects. In most solutions I've used the IoC container mainly for the services around those. My domain objects are usually created and destroyed as part of the program flow and I usually use separate factory objects for this. The factories can then be injected and handled by the IoC container. I've had some success creating factories as wrappers around the IoC container. This way the container handles lifetime of the domain objects too.

This is a very interesting question. If I look back at the way I've implemented things in the past I can see a trend towards smaller and more granular interfaces and classes. Things certainly got better this way. I don't think the optimal solution has one function per class though. This will effectively mean you're using an OO language as a functional language and while functional languages are very powerful there is a lot to be said for combining the two paradigms.

Mendelt
Great answer. We group similar services by package. We don't DI our domain objects but we have started creating a lot of *small* State-objects (part of a context for a scenario if you like) that we inject into services. These state objects have state/domain objects needed for that specific context.
krosenvold
I don't think one function per class is correct either. That's really what I'm asking about ;)
krosenvold
I find that most services are stateless. If I write a service, it probably has only DAOs as private data members. If that's true for you, I think that invalidates the "state and identity" argument.
duffymo
@duffymo services usually do not need identity. I find that services often need some state though to maintain connections for example. Usually this is not really a problem because of the simple lifecycle of a service, they're mostly singletons.
Mendelt
@krosenvold Software design is always balancing forces. It seems one force pushing us toward bigger service classes has gone away with simple DI. Now it seems the major forces we have left are flexibility of many small classes vs the maintainability of grouping functionality into larger classes.
Mendelt
I strongly agree with Mendelt's Identity and Maintenance reasons. Not that silly illustration : a phone provides stateless services "dial number", "dial memory#1", "dial memory #?",... both sharing the same wire. Having one actual handset per service would be a little bit cumbersome.
Olivier
+1  A: 
  • Why are we grouping things in classes and what should the principles be ?

Are you stressing the, "Grouping," or the, "Classes?"

If you're asking why are we grouping things, then I'd second Medelt's, "Maintainability," though I'd rephrase it as, "To reduce the potential cost of ripple effects."

Consider, for a moment, not the actual coupling between your components (classes, files, whatever they may be) but the potential coupling, that is, the maximum possible number of source code dependencies between those components.

There is a theorem which shows that, given a chain of components a - b - c - d - e, such that a depends on b, etc., the probability that changing e will then change the c component cannot be greater than the possibility that changing e will then change d. And in real software systems, the probability that changing e will affect c is usually less than the probability that changing e will affect d.

Of course, you might say, that's obvious. But we can make it even more obvious. In this example, d has a direct dependency on e, and c has an indirect (via d) dependency on e. Thus we can say that, statistically, a system formed predominantly of direct dependencies will suffer from a greater ripple effect than a system formed predominantly from indirect dependencies.

Given that, in the real world, each ripple effect costs money, we can say that the cost of ripple effect of an update to system formed predominantly of direct dependencies will be higher than the cost of ripple effect of an update to system formed predominantly of indirect dependencies.

Now, back to potential coupling. It's possible to show that, within an absolute encapsulation context (such as Java or C#, where recursive encapsulation is not widely employed) all components are potentially connected to one another via either a direct dependency or an indirect dependency with a single, intermediate component. Statistically, a system which minimises the direct potential coupling between its components minimises the potential cost of ripple effect due to any update.

And how do we achieve this distinction between direct and indirect potential coupling (as if we haven't already let the cat out of the bag)? With encapsulation.

Encapsulation is the property that the information contained in a modelled entity is accessible only through interactions at the interfaces supported by that modelled entity. The information (which can be data or behavior) which is not accessible through these interfaces is called, "Information hidden." By information-hiding behavior within a component, we guarantee that it can only be accessed indirectly (via the interfaces) by external components.

This necessarily requires some sort of grouping container in which some sort of finer-grained functionality can be information-hidden.

This is why we are, "Grouping," things.

As to why we're using classes to group things:

A) Classes provide a language-supported mechanism for encapsulation.

B) We're not just using classes: we're also using namespaces/packages for encapsulation.

Regards,

Ed.

Ed Kirwan