Suppose you have a Product
entity and that you want to implement a method which sends the product to a customer (let’s call it ship
). For that method to work, you need a service for calculating prices and a goods transporting service. To wire the services nicely, the natural choice nowadays is a DI framework (Seam, Guice, Spring, CDI, …).
Chances are high that to implement the above scenario, you would create a ProductService
or a ProductManager
(or if you have more specialized services, a ProductShipper
) which would look more or less like this:
public class ProductService {
// Here we can use setter injection, constructor injection, etc., doesn't matter
@Inject private PriceCalculatorService priceCalculator;
@Inject private TransportService transport;
public void ship(Product product, Customer where) {
int price = priceCalculator.calculate(product);
// (...)
transport.send(address, product);
}
// (...)
}
The main problem with that solution is that the product is always passed as an argument.
Check in your project: if you’re using DI, and you have an X
entity, do you have an XService
or XManager
with lots of method where X
is the first argument?
Wouldn’t it be better if you could create something like this:
public class ProductShipper {
private final Product product;
public ProductShipper(Product product) { this.product = product; }
public void ship(Customer where) {
// ???
}
}
The previous way is more procedural: the service/manager is a set of procedures you can run, where the execution takes a product and a customer as arguments. Here we have a more OO approach, which has many benefits:
- the product entity is encapsulated in the service
- you can pass the shipper object around
- you control where and how the shipper object is created
- you can create multiple copies of shipper objects, for multiple products
- etc
I’m not saying that achieving the above is not possible with a DI framework; only that DI encourages the ProductService
approach: it’s just easier to code it procedurally, instead of e.g. creating a ProductShipper
factory, passing all the needed dependencies to it and so on.
An obvious problem with the new approach is that if you create a ProductShipper
object yourself, you won’t have dependencies injected. For DI to work, the object must be managed by the container.
Hence we come to some problems with DI frameworks:
- for DI to work, the objects must be managed by the container. Creating an object using some data not available upfront and having dependencies injected is not possible
- there can only be one (managed) instance of a class available at a single “execution” (for web applications, this will be per one request)
- managed objects are either stateless, or holders of “anemic” data objects; extra steps are required to create “rich” objects (if you have two
Product
entities, you won’t be able to create twoProductShipper
objects with that products encapsulated, but you can create aProductsToShipHolder
managed object into which you can add the products to ship)
How to solve these problems?
I think a good starting point is something that I call object services. Using them, you can inject a provider, with which you can obtain a service, given an entity (or any other object). The good side is that injection into the obtained services works normally. Following our example, the code could look like this:
public class UserInterface {
// OSP stands for Object Service Provider
@Inject OSP<Product, ProductShipper> shipperProvider;
public void shipClicked() {
shipperProvider.f(getProduct()).ship(getCustomer());
}
}
// OS stands for Object Service
public class ProductShipper implements OS<Product> {
private Product product;
public void setServiced(Product product) { this.product = product; }
// Here we can use setter injection, constructor injection, etc., doesn't matter
@Inject private PriceCalculatorService priceCalculator;
@Inject private TransportService transport;
public void ship(Customer where) {
int price = priceCalculator.calculate(product);
// (...)
transport.send(address, product);
}
}
The good thing here is that shipperProvider.f(getProduct())
is a regular object, which has the product encapsulated. It can be freely passed around, and you can create multiple shippers for different products.
(Another good thing here is that object services support polymorphism, so you can get different services injected for different objects!)
However this implementation still has some drawbacks; for example for injection to work the objects still have to be obtained using the provider, so that they are managed by the container. In reality most objects that are created are not managed by the container. How to obtain dependencies there? Normally they are passed e.g. in constructors, but then if suddenly an object deep down the hierarchy needs a dependency we are in trouble, and end up adding a new constructor parameter all the way up till we get to a managed object.
So stay tuned for more … :)
Adam
comments powered by Disqus