Keep Documentation up-to-date with Deptrac

Adrian Philipp
TicketSwap
Published in
4 min readJul 20, 2018

Every day, in every software project, I structure code in a certain way. There are always reasons why the code is structured in that way. One reason might be that the team agreed to follow a certain design pattern. Another reason might be that certain performance characteristics need to be met.

Architecture Decisions

In many software projects that I worked on, the reasons why code is structured in a certain way are not immediately visible. Having the reasons visible would give developers context and a better understanding of the code. This is helpful with:

  • on-boarding of new developers
  • aligning a team on architecture decisions
  • takeing reasons into consideration when changing software
  • keeping long-term goals in mind

At TicketSwap we started documenting these reasons in a format called ADR (Architecture Decision Records). These are lightweight documents which give a reason and context to an architecture decision. Documentation is great, but it has a risk of getting outdated. How can we help ourselves to ensure ADRs are updated?

One helpful tool in the PHP community is sensiolabs-de/deptrac.

Deptrac

This CLI tool allows you to define layers and code structure rules in YAML. It can validate that these rules are always met by integrating a validation step into the CI pipeline.

Not all architecture decisions are about code structure. But for the ones that are, the tool can help to ensure that the underlying ADR is up to date and implemented. Using Deptrac saves time in code reviews because I don’t have to check that the ADRs are followed.

Example: Domain Separation

Within the context of a financial service, we defined separate domains like customers, payments, payouts, refunds etc. We want to keep each domain isolated to battle complexity as the service grows.

What does this mean in practice? Let’s only focus on the customer domain for this example. Because we use the command and event pattern, this means for us:

  • customer controllers can only use customer commands
  • customer command handlers handle only customer commands and only publish customer events
  • customer subscribers only handle customer events

In Deptrac you can define controllers, command handlers, commands, etc. as layers. I’m using className collectors to assign all classes to a layer that match a certain regular expression. In a file domain_customer.yml all layers relevant for this particular customer domain separation are defined like this:

layers:
- name: CustomerController
collectors:
- type: className
regex: .*Controller\\.*Customer\\.*
- name: CustomerCommandHandler
collectors:
- type: className
regex: .*Domain\\Handler\\Customer.*
...

The dependencies between layers are defined in a ruleset. This describes which layer may depend on which other layers. In this example, it looks like this:

ruleset:
CustomerController:
- CustomerCommand
- CustomerEntity
CustomerCommandHandler:
- CustomerCommand
- CustomerEvent
- CustomerEntity
...

When running the deptrac analyze command all code files are scanned and validated with the layers and ruleset previously defined.

deptrac analyze domain_customer.yml --formatter-graphviz-display=0

If we would raise an event from the payout domain in a customer command handler, we would get an error like this:

Domain\Handler\Customer\SavePayoutMethodHandler::12 must not depend on Domain\Event\Payout\PayoutAvailableEvent (CustomerCommandHandler on NotCustomerEvent)

The PayoutAvailableEvent is not a customer event but we raise it in a customer command handler.

This is one example for a quite technical reason for structuring code in a certain way. But this doesn’t always have to be this way.

Another example that has business reasons is in the context of this financial service and integration with third-parties. We want be able to anticipate on changes in regulations or payment platforms during the lifetime of the service. To make sure we can easily switch out payment platforms, we have a ruleset that validates that the payment platform code is isolated.

Continuous Integration

After defining rulesets, it is important to make sure that they are validated all the time. For that, we added Deptrac to our CI pipeline running on CircleCI. To install Deptrac I added this step in the CircleCI configuration:

- run:
name: Setup Deptrac
command: |
set -exu
if [ ! -f ./vendor/bin/deptrac ]; then
curl -LS http://get.sensiolabs.de/deptrac.phar -o ./vendor/bin/deptrac
chmod +x ./api/vendor/bin/deptrac
fi

And to validate, I added a validation step which runs each ruleset:

- run:
name: 'Validate Deptrac'
command: |
set -exu
vendor/bin/deptrac analyze tests/Deptrac/domain_cart.yml --formatter-graphviz-display=0
vendor/bin/deptrac analyze tests/Deptrac/domain_customer.yml --formatter-graphviz-display=0
vendor/bin/deptrac analyze tests/Deptrac/platform.yml --formatter-graphviz-display=0
...

Summary

Documenting why a decision was made is helpful for new developers and alignment in teams. Architecture decisions that are about code structure can be validated automatically using Deptrac. This frees you from having to check this manually. It’s also a great way to make sure that the documentation matches reality. The Deptrac rulesets can either directly link to an ADR or explain the reasoning in a comment.

Are you interested in working with me? Join us at TicketSwap.

--

--

Adrian Philipp
TicketSwap

Software Developer @TicketSwap: The safest way to buy & sell tickets https://www.ticketswap.com