The kind of design we want within a service

(This is the continuation of a conversation we were having on a review.)

Hi Soldevelo team,

Josh and I realized we were going in circles on the meta-point of what kinds of diagrams to use, when we should be more clearly focusing on the real goal, which is communicating what kind of design we’re expecting to see.

Here is an (incomplete) UML class diagram that we sketched on the whiteboard today. It should illustrate the direction we want things to go. Some underlying points:

(1) we don’t want all the business logic in one massive Service, but rather it should be in specific classes that can be easily and individually unit tested

(2) because of the extensibility approach we have chosen, everything that we might eventually like to be overridable should be an interface with a default implementation as a Spring bean

Describing what’s in the image…

We have a Service, which is mainly focused on querying and saving data, and we avoid putting much business logic there.

We have some domain classes like Requisition, RequisitionLineItem, and RequisitionEvent. If we have business logic that is not configurable, and doesn’t require hitting any other services, that business logic should live in the domain objects (e.g. Requisition.validateLineItems() in this example).

When a piece of business logic needs to hit a service (and/or if it’s a likely candidate to be extensible via the ExtensionManager mechanism) then we define it as an interface, and there should be a default implementation provided in the requisition service. (We haven’t explicitly drawn the default classes here, but for example we’d have @Component class DefaultRequisitionValidator implements RequisitionValidator.)

A couple random small points:

  • Requisition.validateLineItems() vs RequisitionValidator: the idea is that, where possible, logic belongs in a domain object. But logic that would hit a service (e.g. cross-check to prevent duplicating an existing requisition) can’t happen in the domain object itself.

  • Since the desired workflow is that a requisition may have many Authorize, Authorize, Reject, Approve, etc, this should be explicitly represented (as events) rather than just having a single status field.

Hopefully this clarifies the kind of design we’re aiming for! Let us know if you have any questions.

-Darius & Josh pairing

···

Darius JazayeriPrincipal Architect - Global Health
Email
djazayeri@thoughtworks.com

Telephone
+1 617 383 9369

ThoughtWorks

Hi Darius,

I understand we can move uncomplicated code of business logic to domain classes. If business logic is more complicated, we can move code to other classes like RequisitionCreator.

First question:
1)Is it possible that we do:

class RequisitionCreator {

Requisition initiate(Requisition requisition) {
    //some code of busisness logic...

    requisition.initiate2();  //part of busisness logic in domain class

     //some code of busisness logic...
}

}

or just:

class RequisitionCreator {

Requisition initiate(Requisition requisition) {
    //all code of busisness logic here...
   
    //no initiate2() method in requisition
}

}

next questionn:
2) How the Requistion Worklow will look like (option 1 or 2)?


Does this make sense?

Przemysław Studziński