On October 25th, we had the opportunity to give a talk. We decided to speak about how our code has evolved from the early days, when we programmed in a framework-coupled manner, to today, where we attempt to decouple business logic from the infrastructure it requires, including the entry point so that we can reuse use cases. In summary, we reviewed SOLID, testing, hexagonal architecture, and microservices.

The event was organized by the folks at Nubelo, to whom we thank for coordinating all the management :) . We also thank the people at IronHack, who provided the venue for the event and recorded the session. Lastly, also thanks to Moritz, who provided the beers to liven up the third half :D

Having made the introduction, let’s get to the point!
Framework Coupled Code
The peculiarities of this stage can be seen in the video from minute 6:50. We mainly talked about the lack of applying principles that promote the cohesion, maintainability, and changeability of our application.
Controller
As you can see in the example, the peculiarities of this type of codebase go beyond just being tied to the framework. As we see, we would be using Doctrine (the application’s ORM) directly, without implementing any level of indirection as a contract in order to decouple ourselves. These kinds of things concretely define aspects like changeability. Furthermore, we would be executing all the logic of the use case in the controller itself, thus penalizing the possibility of reusing this logic from other entry points to the application. We’ve addressed these concepts in previous videos about SOLID:
- Single Responsibility Principle SRP
- Interface Segregation Principle ISP
- Common mistakes in designing Interfaces
- Dependency Inversion Principle DIP
// CourseController.php
<?php
namespace AppBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
final class CourseController extends FOSRestController
{
public function getCourseAction(Request $request)
{
return $this->getDoctrine()
->getEntityManager()
->createQueryBuilder()
->select('c', 'v')
->from('AppBundle\Model\Course', 'c')
->where('c.level', '>', $request->get('from_level', 0))
->getQuery()
->execute();
}
public function getCourseVideosAction($courseId)
{
return $this->getDoctrine()
->getEntityManager()
->createQueryBuilder()
->select('c', 'v')
->from('AppBundle\Model\Course', 'c')
->leftJoin('a.Video', 'v')
->where('c.id', '=', $courseId)
->getQuery()
->execute();
}
}
Test
At minute 12:25, we start talking about the implications this type of code has concerning testing. If we’ve been generating coupled code, it will be harder to test. The previous type of code often leads to tests like the following. Instead of testing our real business logic, we are testing whether the ORM actually does what it claims to do. In other words, this type of test does not add any value. Additionally, as we can see, we are making our test extremely fragile as we couple it to the internal guts of the ORM. This means that if the way the framework or the ORM works changes, we will need to change our tests. 💩! We have discussed these concepts at one time or another in the testing videos:
- How to test coupled code
- #naveMisterioCodelyTV on how to listen to our tests
- #naveMisterioCodelyTV on emergent designs from TDD
// CourseControllerTest.php
<?php
namespace AppBundle\Tests\Controller;
use AppBundle\Controller\CourseController;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\QueryBuilder;
use PHPUnit_Framework_TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
final class CourseControllerTest extends PHPUnit_Framework_TestCase
{
public function testGetCoursesFilteredByLevel()
{
$fromLevel = 0;
$request = new Request(['from_level' => $fromLevel]);
$container = \Mockery::mock(ContainerInterface::class);
$doctrine = \Mockery::mock(Registry::class);
$entityManager = \Mockery::mock(EntityManager::class);
$queryBuilder = \Mockery::mock(QueryBuilder::class);
$query = \Mockery::mock(AbstractQuery::class);
$container->shouldReceive('has')
->once()
->with('doctrine')
->andReturn(true);
$container->shouldReceive('get')
->once()
->with('doctrine')
->andReturn($doctrine);
$doctrine->shouldReceive('getEntityManager')
->once()
->withNoArgs()
->andReturn($entityManager);
$entityManager->shouldReceive('createQueryBuilder')
->once()
->withNoArgs()
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('select')
->once()
->with('c', 'v')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('from')
->once()
->with('AppBundle\Model\Course', 'c')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('where')
->once()
->with('c.level', '>', $fromLevel)
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('getQuery')
->once()
->withNoArgs()
->andReturn($query);
$query->shouldReceive('execute')
->once()
->withNoArgs()
->andReturn(
[
[
'title' => 'Codely is awesome',
'level' => 2,
],
[
'title' => 'Learn to say basically like Javi',
'level' => 5,
],
]
);
$controller = new CourseController();
$controller->setContainer($container);
$controllerResult = $controller->getCourseAction($request);
$this->assertEquals(
[
[
'title' => 'Codely is awesome',
'level' => 2,
],
[
'title' => 'Learn to say basically like Javi',
'level' => 5,
],
],
$controllerResult
);
}
}
Hexagonal Architecture + CQRS + Domain-Driven Design Modules
Once we have seen the peculiarities of a design tightly coupled to the framework, and a testing strategy with which we are really not getting the value we believe we obtain, we move on to a second phase. In this next phase, explained from minute 15:55 of the video, we see a type of code that introduces a whole series of concepts in the form of conventions and layers of indirection not present in the previous phase, but which, in return, have their benefits as we will see :D Summary:
- Before, 0 levels of indirection: Controller (executes logic)
- After, 3 levels of indirection: Controller (builds command and throws it onto the bus) -> Command Bus (carries the command to its handler) -> Command Handler (instantiates domain models based on command data and invokes the Application Service) -> Application Service (executes logic)
As always, in the end, we are the ones who know our context best, and we must decide whether it makes sense to add all this complexity or not. Therefore, let’s analyze each of these elements and see what benefits they bring and whether they are worthwhile in our context.
Controller
Now, our Controller only has the responsibility of linking the framework (entry point to the application) and the use case to be executed. In fact, it does not do this directly by targeting the Application Service, but rather it only builds a Command in the form of a DTO and throws it onto a Command Bus. This bus is what truly has the associations between which Command Handler knows how to handle each of the Commands.
// VideoController.php
<?php
namespace CodelyTv\Api\Controller;
use CodelyTv\Context\Meetup\Module\Video\Domain\Create\CreateVideoCommand;
use CodelyTv\Infrastructure\Bus\Command\CommandBus;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
final class VideoController extends Controller
{
private $bus;
public function __construct(CommandBus $bus)
{
$this->bus = $bus;
}
public function createAction(string $id, Request $request)
{
$command = new CreateVideoCommand(
$id,
$request->get('title'),
$request->get('url'),
$request->get('course_id')
);
$this->bus->dispatch($command);
return new Response('', Response::HTTP_CREATED);
}
}
Command Handler
This additional level of indirection that we have introduced also serves to translate the data in the command into the domain objects that the Application Service expects. This detail is important because it allows us to launch commands across different modules/contexts/services that may not even share a programming language. Once it translates the raw data into domain models, it also knows which Application Service to execute and invokes it.
// CreateVideoCommandHandler.php
<?php
namespace CodelyTv\Context\Meetup\Module\Video\Domain\Create;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoId;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoTitle;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoUrl;
use CodelyTv\Shared\Domain\CourseId;
final class CreateVideoCommandHandler
{
private $creator;
public function __construct(VideoCreator $creator)
{
$this->creator = $creator;
}
public function __invoke(CreateVideoCommand $command)
{
$id = new VideoId($command->id());
$title = new VideoTitle($command->title());
$url = new VideoUrl($command->url());
$courseId = new CourseId($command->courseId());
$this->creator->create($id, $title, $url, $courseId);
}
}
Application Service
By introducing the concept of an Application Service, where we actually implement the logic of the use case, we ensure that it can be leveraged from other entry points. Besides executing the relevant business logic, the Application Service acts as a barrier at the transaction and event publishing level. In other words, it must guarantee atomicity when executing a particular logic, thus ensuring that if something is executed in our system, it has the side effects it should (like persisting and publishing events, for example).
// VideoCreator.php
<?php
namespace CodelyTv\Context\Meetup\Module\Video\Domain\Create;
use CodelyTv\Context\Meetup\Module\Video\Domain\Video;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoId;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoRepository;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoTitle;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoUrl;
use CodelyTv\Infrastructure\Bus\Event\DomainEventPublisher;
use CodelyTv\Shared\Domain\CourseId;
final class VideoCreator
{
private $repository;
private $publisher;
public function __construct(VideoRepository $repository, DomainEventPublisher $publisher)
{
$this->repository = $repository;
$this->publisher = $publisher;
}
public function create(VideoId $id, VideoTitle $title, VideoUrl $url, CourseId $courseId)
{
$video = Video::create($id, $title, $url, $courseId);
$this->repository->save($video);
$this->publisher->publish($video->pullDomainEvents());
}
}
Test
Finally, it’s also worth noting how our test changes. Now we are indeed testing whether the business logic behaves as we expect. We would be testing from the controller inwards. In other words, we are ensuring that given a command is thrown onto the command bus, the relevant repositories will be called to store the changes, and the appropriate domain events will be published.
// VideoModuleUnitTestCase.php
<?php
namespace CodelyTv\Context\Meetup\Module\Video\Test\PhpUnit;
use CodelyTv\Context\Meetup\Module\Video\Domain\Video;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoRepository;
use CodelyTv\Context\Meetup\Test\PhpUnit\MeetupContextUnitTestCase;
use Mockery\MockInterface;
use function CodelyTv\Test\similarTo;
abstract class VideoModuleUnitTestCase extends MeetupContextUnitTestCase
{
private $repository;
/** @return VideoRepository|MockInterface */
protected function repository()
{
return $this->repository = $this->repository ?: $this->mock(VideoRepository::class);
}
protected function shouldSaveVideo(Video $video)
{
$this->repository()
->shouldReceive('save')
->with(similarTo($video))
->once()
->andReturn($video);
}
}
As these two pieces (repository and domain event publisher) are infrastructure components, and we want to avoid the fragility we observed previously by coupling to the ORM, we apply the Dependency Inversion Principle taken to the level of Ports and Adapters of Hexagonal Architecture. We essentially mock the contracts at the domain level in order to test solely the message passing between our Subject Under Test and its collaborators.
// VideoModuleUnitTestCase.php
<?php
namespace CodelyTv\Context\Meetup\Module\Video\Test\PhpUnit;
use CodelyTv\Context\Meetup\Module\Video\Domain\Video;
use CodelyTv\Context\Meetup\Module\Video\Domain\VideoRepository;
use CodelyTv\Context\Meetup\Test\PhpUnit\MeetupContextUnitTestCase;
use Mockery\MockInterface;
use function CodelyTv\Test\similarTo;
abstract class VideoModuleUnitTestCase extends MeetupContextUnitTestCase
{
private $repository;
/** @return VideoRepository|MockInterface */
protected function repository()
{
return $this->repository = $this->repository ?: $this->mock(VideoRepository::class);
}
protected function shouldSaveVideo(Video $video)
{
$this->repository()
->shouldReceive('save')
->with(similarTo($video))
->once()
->andReturn($video);
}
}
DDD Bounded Contexts and Microservices
From minute 37:38 onwards, we discuss other strategies for structuring our application and what implications they would have at the code and infrastructure levels. One of the risks we encountered with this kind of talk was that, when trying to cover so much, everything could end up feeling too abstract and hard to digest for someone without prior knowledge of the topic. Thus, to try to avoid this, we prepared the following table where we attempt to highlight the key peculiarities of each of the strategies we discussed:

It’s a table that, as we said in the talk, should be taken with a grain of salt. From the moment one synthesizes information to make it more digestible, many nuances and exceptions are lost. And if we also group concepts to highlight the differences between them, it may be interpreted as a sequential succession of states - when in reality, in many cases, they are orthogonal aspects. In any case, we felt it was appropriate to include the table and comment on it because we think it adds value. Having something like this would have helped us back in the day to at least start investigating a bit more and to pull on the thread :) . To date, we have discussed these topics in the introduction video to Hexagonal Architecture, and we plan to explore them much more in future videos, so if you want to stay updated, sign up for the newsletter or subscribe to the YouTube channel:
Material from the Talk
We believe that beyond the session, all the material we prepared for the talk can be useful for clarifying or examining some of the concepts we explained at the implementation level. Therefore, let's take a quick look at all of it 😬
- Slides "From framework coupled code to microservices through DDD"
- Repository with example of coupled code
- Repository with example of implementation based on Hexagonal Architecture, CQRS, and modules (Domain-Driven Design) (Pull Requests are welcome!)
- Video of the session "From framework coupled code to microservices through DDD"
- We also leave you the photos from the event in case you want to take a look :P
After all this effort 😅, just a note that if you found it useful or believe it can help your contacts, please help us reach more people by sharing the article, subscribing to the CodelyTV YouTube channel, or having a beer in our honor 😄
T-shirt Giveaway
And finally, since it was a somewhat special occasion, we decided to do a small giveaway of a CodelyTV t-shirt among the attendees of the event who filled out a form :P

As we just announced on Twitter, the lucky winner was Sergio Susa. Congratulations! :D
Acknowledgments
We would like to close this post by thanking all those people who have helped us learn all these concepts that we can transmit today, as well as the companies that create an environment in which we can experiment with all these kinds of things. From letgo, which is the project we are currently on, to Tangelo, Thatzad, and Uvinum, which are the companies we have been through. Thank you!!!