Blog

Code – How and why to decouple from your database

Iain Cambridge on July 17, 2021

Part of the promise of Parthenon is that it’ll let you grow. The way we aim to achieve that is to decouple our code to any specific database or database library. We feel that this is a major benefit when it comes to giving your application room to grow. We feel so strongly that everyone should apply this approach to their code we wanted to share our approach to supporting Doctrine ORM and ODM within Athena.

Starting Off With The Interface

Starting out with the interface means we define our contract and our code only couples to the contract. In the Athena’s repository interface we define a few methods and ensure that we encapsulate the database logic within the interface. The interface only accepts generic types or classes that belong to Athena.

<?php

/*
 * Copyright Humbly Arrogant Ltd 2020-2021, all rights reserved.
 */

namespace Parthenon\Athena\Repository;

use Parthenon\Athena\ResultSet;
use Parthenon\Athena\Filters\FilterInterface;
use Parthenon\Common\Exception\NoEntityFoundException;

interface CrudRepositoryInterface
{
    public const LIMIT = 10;

    /**
     * @param FilterInterface[] $filters
     * @param null|mixed $lastId
     */
    public function getList(array $filters = [], string $sortKey = 'id', string $sortType = 'ASC', int $limit = self::LIMIT, $lastId = null): ResultSet;

    /**
     * @param mixed $id
     *
     * @return mixed
     *
     * @throws NoEntityFoundException
     */
    public function getById($id, $includeDeleted = false);

    public function save($entity);

    public function delete($entity);

    public function undelete($entity);
}

Time to decouple from Doctrine ORM

We then work on the doctrine ORM side of things. For the sake of brevity, we’ll just show and explain the class that provides the basic encapsulation instead of showing the entire DoctrineCrudRepository. We start by creating our own custom ServiceEntityRepository that gives access to the EntityManager.

The reason for this is that we don’t want to have to extend the doctrine class itself and implement our repository interface on the doctrine class. We want to completely wrap the Doctrine class and ensure that the class that implements our repository interface. It is quite common to accidentally leak Doctrine ORM out of your repository because often what is done is extending a Doctrine class that provides more methods than is defined in the interface.

<?php

/*
 * Copyright Humbly Arrogant Ltd 2020-2021, all rights reserved.
 */

namespace Parthenon\Common\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

class CustomServiceRepository extends ServiceEntityRepository
{
    public function getEntityManager()
    {
        return parent::getEntityManager();
    }
}

Then we create a class that will implement the RepositoryInterface. So our class here only implements the RepositoryInterface and then accepts the custom Doctrine ORM ServiceRepository. This allows us to use the Doctrine ORM within the safety of our DoctrineRepository class. We can access the entity manager, use all the repository methods that doctrine provides such as find or findOneBy.

<?php

/*
 * Copyright Humbly Arrogant Ltd 2020-2021, all rights reserved.
 */

namespace Parthenon\Common\Repository;

use Parthenon\Common\Exception\NoEntityFoundException;

class DoctrineRepository implements RepositoryInterface
{
    protected CustomServiceRepository $entityRepository;

    public function __construct(CustomServiceRepository $entityRepository)
    {
        $this->entityRepository = $entityRepository;
    }

    public function save($entity)
    {
        $em = $this->entityRepository->getEntityManager();
        $em->persist($entity);
        $em->flush();
    }

    public function findById($id)
    {
        $entity = $this->entityRepository->find($id);

        if (!$entity) {
            throw new NoEntityFoundException('No entity found for id '.$id);
        }

        return $entity;
    }
}

Time For A Custom Data Source

Moving over to different database systems is part of growing and scaling an IT system and why decoupling from your database and database libraries is so important. With this process, all you need to do is to implement the CrudRepositoryInterface to enable a new database system with the Athena CRUD system. Enabling the changing of a database system without making colleagues swap systems or building your own CRUD system. For the sake of brevity, we’ll just share some pseudo-code as implementing a repository class can be a lot of work.

<?php 

use Parthenon\Athena\Repository\CrudRepositoryInterface;

class ACustomCrudRepository implements CrudRepositoryInterface {
    public function save($entity) {
        $this->sendHttpRequest('POST', 'http://data/'.$entity->getId(), json_encode($entity));
    }

    public function delete($entity) {
        $this->sendHttpRequest('DELETE', 'http://data/'.$entity->getId());
    }
}

How does decoupling from your database help you?

By using this process, you’ll be able to decouple your application from your database systems and database libraries. This will allow you to build a future-proof system where you’ll be able to make changes to the dependencies your system has without a lot of fuss and hassle costing tens of thousands. Enabling them to work on more pressing things. Because of this approach, we’re very confident that any database can be used with Athena.

You can find more information about our repository usage at https://docs.getparthenon.com/common/repositories/

Parthenon

Get the advantage by keeping up to date on how tech can help your business and learn technical things in a non-techincal way by subscribing to our weekly newsletter.