Question

I'm discovering craftsmanship and trying to learn it, and I decided first to understand how to work with the SOLID principles.

I'm actually facing some troubles while dealing with the Open/Closed principle.

How I'm working

In fact, I'm usually building applications like the following:

  • Presentation layer with Angular
  • Business layer with Node.js (using service / repository)
  • We abstract the DAL for now, just for learning purposes

Actually, I used to put my business rules in my services, using multiple if and else statements. But the fact is that I'm really open to modification but not to extension in that case. I want to change that, in a good and open way.

My rules

Let's take a simple example where in my application I have to collect a mail address that has to match the following business rules:

  • The domain should be example.com
  • The first part of the mail address should contain a "." like "firstname.lastname"
  • The first part of the mail address should be less than 100 chars

How I tried to refactor my multiple if statements:

class BusinessMail{

  checkMail(){
      const ruleArray = []
      ruleArray.push(new MailCheckDomain(mail))
      ruleArray.push(new MailCheckLength(mail))
      ruleArray.push(new MailCheckDot(mail))

      for(const rule of ruleArray){
        if(!rule.apply()) return false
      }

      return true
    }

}

Right there, I'm leaving my if / else statements. But my mail checking function is really open to modification, because if I need to have a rule, I have to have it in my function body.

Another solution would be to inject my array into my service:

class BusinessMail {

  constructor (mailRules = []) {
    this.mailRules = mailRules
  }

  checkMail () {
    for (const rule of this.mailRules) {
      if (!rule.apply()) return false
    }

    return true
  }

}

This way, I'm not forced to create my rules inside of my service.

But what if I have a big amount of stuff inside of this class? I mean, my business domain is really small in that case. But what if I have a service that has to check like 50 different rules, on different functions? It means that I would probably have to initialize multiple arrays of rules.

I don't feel good with this solution and I'm looking for something better if possible.

Does anybody know how should I do this to have my OP principle applied on my business layer without this feeling of "no trust"?

Was it helpful?

Solution

You're on a good way there, but what happened to you will happen again and again: Once you start concentrating on principles like SOLID and refactoring your code to be better in some way, something strange happens - you start to see things.

Sounds spooky and feels like that at times even. The point, however, is that an improved design, f.ex. via SOLID principles, makes it much easier to see vital points that you didn't see before.

For example, you now complain that you have some 50 different rules that would need to go into your constructor. Contrast that to the situation prior to your change - you had those very same 50 rules hidden, embedded and implied somewhere within the class. They have been there all the time, but now you can actually see them. Why didn't it bother you before?

How do you deal with such problems as a craftsman? Simple: Accept it as a fact, then rinse and repeat. You solved the problem of not being open to extension, now you just have another problem to solve.

Here's an idea for a straightforward and typical solution: You are passing in an array of conditions that an e-mail address needs to satisfy. Conditions (or predicates as they are sometimes called in that context) have really awesome compositionality. if (a && b) is easy to understand, but ruleA && ruleB is possible in just the same way! Some languages even have built-in operations for this (f.ex. in Java you have Predicate#and, #or, etc.).

My point being: You don't pass 10 rules for verifying your e-mail, but instead you pass a single e-mail verification condition. Instead of the for-loop you simply apply that single condition, which in itself applies all the other conditions.

I can't make similar suggestions for the other 50 rules you have, since I have obviously no idea what they are. The general scheme remains though: You have an improved version of your code, saw new problems and need to find new solutions to them.

And a final important remark: Do not go overboard on this! Make sure that the effort you put in is justified, since this improvement cycle can basically go on forever. The better a craftsman you become, the more problems will become apparent to you and the more work there would be to do. Part of working professionally though is to also evaluate the merit of this work. Improving code of an existing and working feature is not gaining the company anything immediately. It may pay off in the future with fixes/enhancements/etc., but it may as well not. This sort of judgement is really hard and you need lots of experience. If you're just getting started in that direction, I suggest you get a mentor to support you.

Licensed under: CC-BY-SA with attribution
scroll top