You want to pull videos from a KiKA mediathek section, but only the ones aimed at older kids — no preschool.
Challenge
FSK looks like the field for the job. It isn’t. Most of the catalogue carries no rating at all, and where one exists it doesn’t separate the age bands: the same FSK 0 covers both a preschool cartoon and a live-action film for eleven-year-olds.
In Part 1, we laid the foundation for a vertical architecture of our still muddy monolith. We are now able to enforce rules between PHP namespaces with deptrac.
But we can only enforce which namespaces talk to other namespaces; we don’t have a defined way yet. Let’s assume each namespace is a bounded context. Then other namespaces or bounded contexts should not simply be allowed to call every class of a bounded context, right? They should only be allowed to talk to the bounded context via a defined interface, or in Ports and Adapters speak, a driving port.
Ports and Adapters defines the inside and the outside of a hexagon (in fact the hexagon is arbitrary here): The inside contains the business logic and rules, entities, events, and so on. The boundary is usually a Bounded Context. The outside of the hexagon is technical infrastructure.
The left side, the “driving side”, is the inbound side—everything that talks to the hexagon. That could be your application’s REST or GraphQL API, or in monoliths, other bounded contexts. The right side is the “driven” side—everything the hexagon needs to work, for example databases, APIs, or in a monolith, other bounded contexts.
Step 1: Extract “driving” port interface in the Payment namespace
Let’s do this with our Payment from the example in the first post. We don’t want PaymentGateway::charge() to be called directly from outside the Payment namespace, but only via a defined interface.
Let’s call the interface Payment\DrivingPort\ForPaymentUseCase - which indicates it’s for one specific use-case:
Step 2: Adapt deptrac to enforce the rule
First, let’s create a new layer in deptrac.yaml called Payment_DrivingPorts.
Finally, we need to adapt the “allowed layers”, so that the Order layer (in our case: Bounded Context) is allowed to call the Payment layer/bounded context via its driving ports:
1 2 3 4 5
Order: - Infrastructure - Payment_DrivingPorts # allow the Order domain to call Payment Ports Payment: - Payment_DrivingPorts # necessary to allow implementing the interface
Running deptrac will produce an error now (as we have not adapted the code yet):
1 2 3 4 5 6 7
-------------------------- ---------------------------------------------------------------------------------------------------------------------- Reason Order -------------------------- ---------------------------------------------------------------------------------------------------------------------- DependsOnDisallowedLayer DeptracPortsAdaptersSample\Order\OrderProcessor must not depend on DeptracPortsAdaptersSample\Payment\PaymentGateway You are depending on token that is a part of a layer that you are not allowed to depend on. (Payment) /var/www/html/src/Order/OrderProcessor.php:10 -------------------------- ----------------------------------------------------------------------------------------------------------------------
Step 3: Allowing existing violations, but no new ones
If you’re trying to introduce this architecture into an existing piece of software, you will probably get a lot of violations at this step. But don’t give up—Deptrac can generate a baseline file with known violations:
1 2 3
$ docker compose run --rm php vendor/bin/deptrac analyze --formatter=baseline
Baseline dumped to /var/www/html/deptrac.baseline.yaml
The advantage here is that existing violations are allowed and documented, but new violations will be detected (esp. when running deptrac as part of the CI workflow).
Step 4: Adapt code to fit into the architectural style
But let’s also fix our known violation now:
The driving port PHP interface looks straightforward
Voilà, the calling code (OrderProcessor) is now decoupled from the inner workings of the Payment namespace.
(Note: The actual wiring is left out here, that’s usually done by a DI container.)
Let’s run deptrac again:
1 2 3 4 5
---------------------------------------------------------------------------------------------------------------------------------------------- Errors ---------------------------------------------------------------------------------------------------------------------------------------------- Skipped violation "DeptracPortsAdaptersSample\Payment\PaymentGateway" for "DeptracPortsAdaptersSample\Order\OrderProcessor" was not matched. ----------------------------------------------------------------------------------------------------------------------------------------------
Great! Deptrac complains that there is a skipped violation that does not exist (anymore, as we just fixed it). So let’s generate a new baseline deptrac analyze --formatter=baseline. For this small example, it results in an empty baseline. In real-world projects we would have at least removed one violation and have made this software a little better (aka the boyscout rule: We left the camping ground a bit cleaner than we found it.)
While Deptrac is commonly used to enforce horizontal boundaries within PHP projects, it can also help define and maintain vertical boundaries (such as Bounded Contexts in DDD speak, or Quanta) in PHP monoliths.
This capability is especially valuable when implementing the Hexagonal aka. Ports and Adapters architecture, ensuring clear separation of concerns and robust modularity in your PHP applications.
This three-part series will cover:
Part 1: Setting up a basic Deptrac configuration for a PHP monolith (this post)
The Lost Update Problem is a common issue in concurrent systems, where two transactions read the same data, modify it, and write it back to the database. The second transaction will overwrite the changes made by the first transaction, causing the first transaction’s changes to be lost.
This is especially problematic in cases where money in a bank account is involved, as it can lead to inconsistencies in the account balance. A rather dramatic example of this is the Flexcoin bankruptcy.
In the world of software development, ensuring the quality and reliability of code is paramount. One popular approach to achieving this goal is Test-Driven Development (TDD), where developers write tests before writing the actual code. TDD is also my default way of approaching software development.
However, I’ve started in many environments situations where code changes are made without accompanying automated tests. In such cases, I am using a method I call “Review-Driven Testing”, which helps not only to improve code quality and upskill developers, but also to promote a testing culture within development teams.
What is Review-Driven Testing?
Review-Driven Testing is a practical approach to adding automated tests to code changes that were initially developed without them. While traditional TDD focuses on writing tests before writing code, Review-Driven Testing involves writing tests after the code has been developed and submitted for review. It can be seamlessly used in teams that are not used to writing automated tests by default (yet).
Code reviews can be very time-consuming. Especially if you are reviewing a lot of code changes. Often, code changes are simple refactorings, such as extracting code into methods/functions or moving code around. These changes are more or less irrelevant for the review, since they usually don’t change the behavior of the code. As a reviewer, one still wants to make sure that code hasn’t been changed during the code move.
This article describes how to adapt the AWS provided upgrade path to work with a CDK / CloudFormation infrastructure-as-code project, which requires a few more steps which I call the “CDK/CloudFormation retain-and-import dance“.
This article demonstrates how to upgrade an CDK (or Cloudformation)-managed AWS Aurora cluster from MySQL 5.7 compatible AWS Aurora 2 to MySQL compatible Aurora 3 engine without any - ok, I lied - one minute of downtime. The process utilizes the blue/green deployment feature of Aurora. It also shows how CDK/CloudFormation stacks are brought back in sync with the upgraded Aurora clusters.
When managing RDS parameter groups with CloudFormation, you might have noticed that changes to a DB cluster or instance parameter group will always cause a reboot of the RDS instance. This happens when the RDS cluster or instance parameter group and the RDS instance are managed by the same CloudFormation stack.