jeudi 31 mai 2018

Business logic and rules - how to decouple them from the domain model

I'm having slight trouble figuring out how to make my design loosely coupled. Specifically how to implement business logic and rules into domain models, as well as where to place the different parts of the code - i.e. folder structure.

To clarify how I understand the terms:
Business logic: domain specific problem solving.
Business rules: domain specific rules.
Domain model: abstractions of domain specific, real world objects e.g. an employee.

So, let's do a simple example

Say we have a company with employees. Every employee must have a security number (business logic). The security number must be at least 10 characters long (business rule).

My shot at modeling this would look something like:

# Conceptual model of an employee within the company
class Employee {

    private $name;
    private $securityNumber;

    // Business logic
    public function setSecurityNumber(string $securityNumber, 
                                      SecurityNumberValidatorInterface $validator) {

        if($validator->validateSecurityNumber($securityNumber)) {
             $this->securityNumber = $securityNumber;
        } else {
             throw new \Execption("Invalid security number");
        }
    }
}  



# Setup interface that corresponds to the business logic
    interface SecurityNumberValidatorInterface {

    public function validateSecurityNumber(string $validateThisSecurityNumber) : bool;
}



# Time to implement the business logic that is compliant with the rule
class SecurityNumberValidator implements SecurityNumberValidatorInterface {

    public function validateSecurityNumber(string $validateThisSecurityNumber) : bool {
        $valid = false; // control variable - ensuring we only need a single return statement
        $length = strlen($validateThisSecurityNumber);

        if ($length < 10) {
            $valid = true;
        }

       return $valid;
    }
}


I see some problems with this approach...

  1. Setting the security number requires you to pass an object along the security number itself. Which I think looks a bit nasty for a setter.
  2. Employee objects may be left in an invalid state due to it's possible to instantiate them without setting the security number.

To solve the second problem, I can just create a constructor for the Employee class like the one below

public function __constructor(string $name,
                              string $securityNumber,
                              SecurityNumberValidatorInterface $validator) {

    $this->name = $name;
    $this->setSecurityNumber($securityNumber, $validator);
}


This may be an antipattern due to calling a setter in the constructor...
What is a nicer approach to this? Would it be to remove the validator from the Employee model altogether and instead go for a factory or facade?

Aucun commentaire:

Enregistrer un commentaire