blog/posts/contract-development.md

151 lines
7.4 KiB
Markdown
Raw Normal View History

2024-07-03 02:59:25 +00:00
---
title: Practical Example of Contract Development
description: A post wherein I describe a practical use-case for developing by contract in PHP
date: 2023-04-09
tags:
- development
- php
layout: layouts/post.njk
---
I have been mulling in my head for a while now about how I wanted to talk about this very common design pattern at work. [Designing by contract](https://en.wikipedia.org/wiki/Design_by_contract) is a very old idea in software and I have found myself having to explain it and teach it other developers at work. So hopefully in this post, I can elaborate on why using a contract is beneficial in the design and development of large applications.
This blog post is definitely not for a seasoned software developer - or at least should not be. Ideally, you're a new to intermediate developer and you're working with PHP and thinking "_Why is my boss making me write all these interfaces?_" and this blog post will give you a practical example of why its important to consider the interface, or contract, of different objects within your application.
## Background
We got the opportunity at work to development a new service from scratch. This service would be replacing two other services by combining them, and ridding ourselves of some ancient (well in software terms) code. Its every developer's dream - we got to rewrite it from scratch! Well, not quite. We needed to maintain the legacy database that drove our entire production facility. Luckily for us, we are will begin the process of redesigning the schema for this database to be more relevant and accurate for our production facility as well as our reporting and metrics needs.
We decided that running an Express.js service with TypeScript was the right way to go as we had a lot of requirements around _events_ and if there is one thing that Node.js is good at - its events. Although, this service at work was written in TypeScript, I am going to give the examples for what we did in PHP. Why? Because its my blog post. But, also, because I feel like this is a topic that isn't talked about often in the PHP world. The examples are also a bit more clear in PHP as I can make use of Namespacing to logically explain how the contracts work.
Finally, I am going to describe a little bit about the Domain of the service. There are two main models that will be talked about in this blog post - the `Order` model and the `Batch` model. An `Order` is a customer purchase of some of our goods. A `Batch` is a logical grouping of our `Order`'s.
## The Contract
So we've got this old database, right? So we are going to write some `Repository` classes that take in a `DatabaseConnection` object that represents an active connection to a MySQL database. These classes will be responsible for actually getting the model data out of the database, and hydrating some models. So, with our goal of having a new database in the future, a legacy database in the now, and new software to write, I recommended we write some strong interfaces for the repositories.
Let's look at an example of the `Order` model.
```php
<?php
namespace Dave\Repository\Api;
use Dave\Model\Api\OrderInterface;
interface OrderRepositoryInterface
{
public function getOrder($id): OrderInterface;
public function updateOrder(OrderInterface $order): boolean;
public function saveOrder(OrderInterface $order): boolean;
}
```
So here we have a contract on what our `OrderRepository` should do. Since we are implementing this interface against an old, legacy database, lets implement it in the following namespace.
```php
<?php
namespace Dave\Repository\OldDatabase;
use Dave\Infrastructure\DatabaseConnection
use Dave\Repository\Api\OrderRepositoryInterface;
use Dave\Model\Api\OrderInterface;
class OrderRepository implements OrderRepositoryInterface
{
/**
* @var DatabaseConnection A Connection to the Old Database
*/
private $databaseConnection;
public function __construct(DatabaseConnection $oldConnection)
{
$this->databaseConnection = $oldConnection;
}
public function getOrder($id): OrderInterface
{
// do something with $this->databaseConnection, like craft a query
$rowData = $this->databaseConnection->query($query);
// and hydrate a model class like, `Order`
$order = new Order($rowData['order_number']);
return $order;
}
// ... implement the other methods
}
```
So above, we can write queries for directly getting the data we need out of the `OldDatabase`. So far, the code must feel pretty redundant and not serve much purpose. This starts to change when we look at using _Dependency Injection_ within the application.
## Dependency Injection
Interfaces can play a huge role in Dependency Injection libraries, like [PHP-DI](https://php-di.org/). We can tell the DI Container "Hey, we have the shape of an `OrderRepositoryInterface` in `Dave\Repository\OldDatabase\OrderRepository`. And the DI Container says: "Awesome, thank you!" and goes on its way, injecting this concrete class wherever the `OrderRepositoryInterface` is asked within the application.
Later on, when we've set up our new `Dave\Repository\NewDatabase\OrderRepository` with out new database schema, we can tell the DI Container to use this concrete clas instead of our old `OldDatabase`.
So, we can set up definition like this within our DI Container.
```php
<?php
$container = new DI\Container();
$database = new DatabaseConnection();
use Dave\Infrastructure\OldDatabase\OrderRepository;
$orderRepository = new OrderRepository($database);
use Dave\Repository\Api\OrderRepositoryInterface;
$container->set(OrderRepositoryInterface::class, $orderRepository);
```
Ah, but now its the future and we have our new database schema. We simply need to implement a new `OrderRepositoryInterface`.
```php
<?php
namespace Dave\Repository\NewDatabase;
use Dave\Infrastructure\DatabaseConnection
use Dave\Repository\Api\OrderRepositoryInterface;
class OrderRepository implements OrderRepositoryInterface
{
/**
* @var DatabaseConnection A Connection to the New Database
*/
private $databaseConnection;
public function __construct(DatabaseConnection $newConnection)
{
$this->databaseConnection = $newConnection;
}
// ... implement the other methods
}
```
This definitely feels like a lot of code, but making sure you're using the `OrderRepositoryInterface` throughout the codebase will allow us to simply change the implementation, and set the new Repository to the DI Container.
```php
<?php
$container = new DI\Container();
$database = new DatabaseConnection();
// this line is the only real change
use Dave\Infrastructure\NewDatabase\OrderRepository;
$orderRepository = new OrderRepository($database);
use Dave\Repository\Api\OrderRepositoryInterface;
$container->set(OrderRepositoryInterface::class, $orderRepository);
```
## Conclusion
I think this is a form of future-proofing that few younger developers think about. I'm sure for my team, after the interfaces were written, they felt it rather redundant to then write out the concrete classes that do the _actual_ logic. It feels like a waste of time - until you need to actually use it. You may have noticed that I made use of the namespace `Api` in the examples. Why? Well, `API` stands for Appliction Programming Interface. I put all of the API for our Repositories (and models for that matter) into the `Api` namespace to keep everything logically structured.