Adam Warski

27 May 2010

Object Services, or bridging anemic and rich models, in CDI/Weld

jee
java

Rich domain models are certainly a nice, object-oriented idea, but I always had one problem with them: what if they become bloated with completely unrelated methods? For objects that are frequently used in a system, we may want to add various methods, which depend on the actual class of the object. Also, what if we’d like to use some other (e.g. CDI) beans as part of the method logic? Normally in DI frameworks there’s no injection into model classes. Or we want to add a frontend-specific method, but we receive the instances from a backend service?

Object Services try to address the issues above. Suppose we have a simple class hierarchy of animals:

abstract class Animal
class Elephant extends Animal
class Ant extends Animal

and we want to implement a paint method, which paints a picture of the given animal on a canvas. Quite obviously, painting an elephant is different from painting an ant. There are several solutions:

  • add a paint method to the Animal interface – problems outlined above
  • add a paint method, in which we check which Animal was passed using instanceof – quite ugly
  • use the visitor pattern – typesafe, but quite verbose

I think the best solution would be to have type-safe “polymorphic extension methods”, so that in your code you could just add some methods to each class in a hierarchy, but unfortunately this isn’t supported by any Java (see also multiple dispatch).

Another possibility is to use what I call “Object Services”. If we want to add some methods to a class hierarchy, we create a parallel hierarchy of “services” (which are normal classes):

interface PaintService<T extends Animal> extends OS<T> { 
   void paint(Canvas c); 
}

// Implements OS<Elephant>
class ElephantPaintService implements PaintService<Elephant> { 
   // Here we can store the object, for which the service was invoked
   void setServiced(Elephant e) { ... }
   void paint(Canvas c) { ... }
}

// Implements OS<Ant>
class AntPaintService implements PaintService<Ant> { 
   // Injection works normally
   @Inject AnthillService anthill;

   void setServiced(Ant a) { ... }
   void paint(Canvas c) { ... } 
}

OS is an interface marking some beans as object services; the class, to which the service corresponds is given as a type parameter.

The ObjectServiceExtension will detect all beans that implement the OS interface, and register an OSP (Object Service Provider) bean which can be later injected to obtain a correct object service given an Animal:

@Inject
OSP<Animal, PaintService<Animal>> paintService;

void paint(Animal a, Canvas c) {
   paintService.f(a).paint(c);
}

Each invocation of the f method will lookup the correct bean, based on the run-time type of the object passed, create a new instance of the found bean and set the object, for which the method was called. All beans created are CDI-managed, so injection etc works normally.

void test(Canvas c) {
   // Will invoke paint(c) in AntPaintService
   paint(new Ant(), c);

   // Will invoke paint(c) in ElephantPaintService
   paint(new Elephant(), c);
}

The source code is available on GitHub in the cdiext project. To use it, just bundle the jar with your application.

Thanks to Tomek Szymański for discussing the implementation.

So what’s next? The code could use a couple of improvements, but the biggest next task is to add deploy-time checking if there’s an object service for each class in a hierarchy (e.g. we have a PrintService and an ElephantPrintService, but forget to add an AntPrintService).

Adam

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?