Friday, June 29, 2007

Workflow engine integration, where does it go?

In one of the projects I have worked on, OSWorkflow is used to manage workflows. It is integrated to the service layer instead of to the domain objects.

The design feels unnatural somehow:

  • The workflow id is stored in the domain object, passed as a parameter in constructor. However, it is not used by the domain object itself. The domain class provides a getter method so that the service layer can retrieve the workflow id and load the workflow instance from OSWorkflow.
  • After loading the domain object and then the workflow instance, the service layer needs to pass to the workflow engine some properties from the domain object and some properties from the DTO that carries the input of the user for the current request, so that the workflow engine can work out what is the next status for the domain object.
  • The domain class also expose a method to allow arbitrary update of the 'status' field so that the service layer can update the domain object with the status that is come up with by the workflow engine. The domain model loses encapsulation.
  • The service layer becomes thick while the domain model becomes anaemic...

IMHO, this design smells.

The change of state in a domain object should be managed by the domain itself. Any application requirements, such as sending email notifications, can be implemented as state change event listeners.

If the workflow logic gets too complicated, workflow engines like OSWorkflow come to rescue. But even if the workflow logic is delegated or outsourced to a workflow engine, it is still domain logic; thus, the workflow engine should be integrated directly into the domain objects instead of to the service layer.

In order for domain objects to work with a workflow engine, domain objects need to have a reference to the workflow engine, which used to be an issue.

With domain object dependency injection feature introduced in Spring 2, this problem is solved. Even if you are using Spring 1.2 or other IoC Containers that do not support domain object dependency injection, or if you are not using an IoC container at all, you can still use Registry pattern where a domain object can look up a dependency.

In conclusion, state change is part of the domain logic, and even if it is outsourced to a workflow engine, the workflow engine integration should still happen in the domain object, without leaking the implementation details to other layers. And technically, there is no more barrier that prevents injecting the workflow engine into domain objects.

No comments: