The Integration Operation Segregation Principle

The Integration Operation Segregation Principle

A few weeks ago I attended a DDDBelgium meetup where I was lucky to participate in a refactor workshop lead by Pim and Joop. After the incredible workshop Pim, Dries and me were discussing some code that we refactored earlier . Not so long in the conversation the words “Integration Operation Segregation Principle” casually got dropped by Pim.

Now I’m going, to be honest with you (as I was with them), I had no idea what the hell he was talking about. And maybe neither do you.

A simple PriceCalculator

Let’s take a look at some code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
declare(strict_types=1);

    namespace Car\Rent;

    final class PriceCalculator
    {
        public function calculate(CarRental $carRental): int
        {
            $startDate = $carRental->getStartDate();
            $endDate = $carRental->getEndDate();
            $days = $startDate->diff($endDate);
            $dayPrice = $days * $carRental->getPricePerDay();

            return $dayPrice + ($carRental->getDistance() * $carRental->getPricePerKm());
        }
    }
As you can see, we have a small piece of code that calculates the price for a few days of rental.

The code itself is pretty straightforward. We gather the start date, the end date. We calculate the number of days and later on multiply those days by the price + the km’s driven. A small piece of code, that does a lot.

No code is complete without a test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
declare(strict_types=1);

    namespace Tests\Car\Rent;

    class PriceCalculatorTest extends TestCase
    {
        /**
         * @test
         */
        public function it_calculates_a_price()
        {
            $carRental = $this->getMockBuilder(CarRental::class)
                ->disableOriginalConstructor()
                ->getMock();

            $carRental->expects($this->once())
                ->method('getStartDate')
                ->willReturn(time());

            $carRental->expects($this->once())
                ->method('getEndDate')
                ->willReturn(time());

            $carRental->expects($this->once())
                ->method('getEndDate')
                ->willReturn(time());

            $carRental->expects($this->once())
                ->method('getPricePerDay')
                ->willReturn(10);

            $carRental->expects($this->once())
                ->method('getDistance')
                ->willReturn(10);

            $carRental->expects($this->once())
                ->method('getPricePerKm')
                ->willReturn(1);

            $calculator = new PriceCalculator();
            $price = $calculator->calculate($carRental);

            $this->assertSame(110, $price);
        }
    }
That’s a big test.

It mocks a few things, then calls the calculator and asserts the results. All in all a very simple unit test, but not a fun one to write.

Turning code into a lot more code

If we take a look at that PriceCalculator class you can see it structurally does 2 things: it fetches data and performs actions on that data. Or in other words: it integrates and operates.

At this point, you probably already figured out when the Integration Operation Segregation Principle is all about: splitting these up. Let me show what the previous class looks like with this principle applied:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
declare(strict_types=1);

    namespace Car\Rent;

    final class PriceCalculator
    {
        private $dateRangePriceCalculator;
        private $distancePriceCalculator;
        private $dayDistanceCalculator;

        public function __construct(
            DateRangePriceCalculator $dateRangePriceCalculator,
            DistancePriceCalculator $distancePriceCalculator,
            DayDistanceCalculator $dayDistanceCalculator
        )
        {
            $this->dateRangePriceCalculator = $dateRangePriceCalculator;
            $this->distancePriceCalculator = $distancePriceCalculator;
            $this->dayDistanceCalculator = $dayDistanceCalculator;
        }

        public function calculate(CarRental $carRental): int
        {
            $startDate = $carRental->getStartDate();
            $endDate = $carRental->getEndDate();
            $days = $startDate->diff($endDate);

            $dayPrice = $this->dateRangePriceCalculator->calculate(
                $days,
                $carRental->getPricePerDay()
            );
            $distancePrice = $this->distancePriceCalculator->calculate(
                $carRental->getDistance(),
    						$carRental->getPricePerKm()
            );

            return $this->dayDistanceCalculator->calculate(
    						$dayPrice,
    						$distancePrice
    				);
        }
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
declare(strict_types=1);

    namespace Car\Rent;

    final class DateRangePriceCalculator
    {
        public function calculate(int $days, int $pricePerDay) :int
        {
            return $days * $pricePerDay;
        }
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
declare(strict_types=1);

    namespace Car\Rent;

    final class DistancePriceCalculator
    {
        public function calculate(int $distance, int $pricePerKm): int
        {
            return $distance * $pricePerKm;
        }
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
declare(strict_types=1);

    namespace Car\Rent;

    final class DayDistanceCalculator
    {
        public function calculate(int $dayPrice, int $distancePrice): int
        {
            return $dayPrice + $distancePrice;
        }
    }
“Frederick you devious hack!” you might shout, You’ve turned a small method into a 4 class method that more than doubles the lines of code.

Well, you’re not wrong. It’s way more code. And on first glance, it looks way more complex. But let’s take a step back and go over it.

Extracting complexity

if you strip away the dependency injection etc, you’re left with

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
    public function calculate(CarRental $carRental): int
    {
        $startDate = $carRental->getStartDate();
        $endDate = $carRental->getEndDate();
        $days = $startDate->diff($endDate);

        $dayPrice = $this->dateRangePriceCalculator->calculate(
            $days,
            $carRental->getPricePerDay()
        );
        $distancePrice = $this->distancePriceCalculator->calculate(
            $carRental->getDistance(), 
                        $carRental->getPricePerKm()
        );

        return $this->dayDistanceCalculator->calculate(
                        $dayPrice, 
                        $distancePrice
                );
    }
There is nothing complex going on here. Everything perfectly describes what is happening. That $dayPrice ? Oh, it’s been calculated, how is it being calculated? Who cares. No need to worry about it. Someone with almost no programming knowledge can read this method and fully understand what’s happening here.

But what about the logic.

1
2
3
4
5
6
7
8
<?php
    final class DateRangePriceCalculator
    {
        public function calculate(int $days, int $pricePerDay): int
        {
            return $days * $pricePerDay;
        }
    }
I would argue that the logic in there is even more simple.

In the future, there might be some business logic changes (we might need to do something with tax?). We now know where we can do that, everything is contained in this little class.

This is also a nice illustration of the Open–closed principle.

So yes, we do have a lot more code. But it’s all very simple code, easy to read, easy to debug and the coolest of all: Easy to test.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    <?php
    declare(strict_types=1);
    
    namespace Tests\Car\Rent;
    
    use Car\Rent\DateRangePriceCalculator;
    
    class DateRangePriceCalculatorTest extends TestCase
    {
        /**
         * @test
         */
        public function it_calculates_a_price_for_a_date_range()
        {
            $dateRangeCaluclator = new DateRangePriceCalculator();
            $price = $dateRangeCaluclator->calculate(2, 50);
    
            $this->assertSame(100, $price);
        }
    }
Now that is a nice unit test. No mocks, easy to write, easy to read. (compare this to the unit test at the top of this article).

Conclusion

As you can see the Integration Operation Segregation Principle is just a long and complicated term to describe something very simple. This all might seem like a lot of work, but it’s worth it. Your code and especially your tests will thank you later

If you want to read more about this, do check out this incredible blogpost: The Incremental Architect´s Napkin - #7 - Nest flows to scale functional design

Related Posts

The Y2k38 Bug: The biggest news craze of the year 2038

The Y2k38 Bug: The biggest news craze of the year 2038

As you might know, I co-organise a PHP meetup called: PHP Antwerp. Some time ago we had one of our talented speakers: Joeri Sebrechts talk about “What every developer should know about time, no excuses“ (If you ever have the chance to see it, I wholly recommend it).

Read More
Communication for team leaders - Trust

Communication for team leaders - Trust

The second part of a three-part"Communication for Team Leaders". This one is about trust, letting go and delegating. I think it’s the hardest one for new managers.

Read More
What is a binary tree and why would I ever want to reverse it

What is a binary tree and why would I ever want to reverse it

You have probably already heard the horror stories of code interviews where they ask you to reverse a binary tree on a whiteboard.

Read More