Closed plumthedev closed 4 months ago
Hi, I see what you are trying to do here, as I personally also structure my projects into modules as you are describing here.
I can share my experience, and how I solve this problem with the current capabilities of deptrac. To me, what are you trying to achieve is too complex for a mark-up language like YAML. Luckily, deptrac also provides a way to specify configuration via PHP. And I think for your use case, it is a natural fit.
$finder = \Nette\Utils\Finder::findDirectories('*')->from('src'); //to get all your modules
foreach($finder as $module) {
//specify layers that are present for each module
//specify rulesets that are present between modules and within modules
//(optionally) specify formatter groups for Graphviz to have nicer graphs
}
I take it a step further and have a deptrac.php
file for each module like this:
//top project level config
$finder = \Nette\Utils\Finder::findFiles('*/deptrac.php')->from('src');
foreach ($finder as $file) {
require_once $file->getPathname();
$namespace = str_replace('src/', '\\My\\Namespace\\', $file->getPath());
$layers = $namespace . '\\layers';
$config->layers(...$layers());
$rulesets = $namespace . '\\rulesets';
$config->rulesets(...$rulesets());
$graphvizFormat = $namespace . '\\graphvizFormat';
$graphvizFormat($formatter);
}
// deptrac.php in each module
/**
* @return list<Layer>
*/
function layers(): array
{
return [
Layer::withName('Branding')
->collectors(
DirectoryConfig::create('src/Branding/.*'),
ClassLikeConfig::create('^Nette\\Routing\\.*'),
ComposerConfig::create()
->addPackage('contributte/menu-control')
->addPackage('nette/routing')
),
];
}
/**
* @return list<Ruleset>
*/
function rulesets(): array
{
return [
Ruleset::forLayer(Layer::withName('Branding'))
->accesses(
Layer::withName('Generic Domain'),
Layer::withName('Nette'),
Layer::withName('Logging'),
//and more
),
];
}
function graphvizFormat(GraphvizConfig $graphvizConfig): void
{
$graphvizConfig->hiddenLayers(Layer::withName('Branding'));
}
Looks great, however I see that $config
and $formatter
are not defined at top level config.
CLI
sail@f7adfa5bd9b5:/var/www/html$ ./vendor/bin/deptrac --config-file=deptrac.php
PHP Warning: Undefined variable $config in /var/www/html/deptrac.php on line 21
PHP Warning: Undefined variable $formatter in /var/www/html/deptrac.php on line 2
/var/www/html/deptrac.php
<?php
declare(strict_types = 1);
$finder = \Nette\Utils\Finder::findFiles('*/deptrac.php')->from('app');
foreach ($finder as $file) {
require_once $file->getPathname();
$namespace = str_replace('src/', '\\My\\Namespace\\', $file->getPath());
$layers = $namespace . '\\layers';
$config->layers(...$layers());
$rulesets = $namespace . '\\rulesets';
$config->rulesets(...$rulesets());
$graphvizFormat = $namespace . '\\graphvizFormat';
$graphvizFormat($formatter);
}
dd($config, $formatter);
Am I doing something wrong? Do you have any examples in docs?
You can reference https://github.com/qossmic/deptrac/blob/2.0.x/docs/blog/2023-05-11_PHP_configuration.md or https://github.com/qossmic/deptrac-src/blob/2.0.x/deptrac.config.php.
From there, you should be able to get the $config
. For formatter - $formatter = GraphvizConfig::create()
.
Thanks @patrickkusebauch It looks definitely like something what fulfill my needs and will be enough here. I will present my solution when it will be built and ready, for now we can close this thread!
Some architectural approaches provide suggestions on how files should be organized within a project. One such approach is building modules based on the Package by Feature principle.
Package by Feature suggests organizing modules based on the functionalities they provide, thereby increasing cohesion and reducing dependencies. Following this approach, we can create the following directory structure:
This structure divides the code into modules: User, Role, and Order. Each module is then divided into specific layers: Application, Domain, and Infrastructure. These layers further break down into specific functionalities such as Service, Dto, and Http.
This structure aligns with the Package by Feature approach and hexagonal architecture principles. It enforces certain rules:
Other modules are aware of the existence of ports and domains of other components but are agnostic to specific implementations. Only the input and output matter for executing an action. This approach helps build modules with high cohesion and low coupling, practically independent of implementations.
However, there is a missing capability in Deptrac to define module names while considering implementation privacy. While the
private: true
option exists, its behavior doesn't fully meet expectations.For instance, if I want to specify that
Order/Infrastructure/Http
can only depend on:Order/Infrastructure/Http
Order/Infrastructure/Service
Role/Application/Service
Role/Domain/Dto
User/Application/Service
User/Domain/Dto
I would have to create six independent layers and specify them in the
ruleset
forOrder/Infrastructure/Http
. This becomes cumbersome, especially considering there could be dozens of such modules, each with several different functionalities (Repository
,Service
,Mapper
,Factory
, etc.).My proposal is to add support for checking code modularization and encapsulating these modules within Deptrac. For example:
The introduced changes include:
public
property for a specific layer to denote the module's public API.With this configuration:
Domain Dto
remains independent.Application Service
depends only onDomain Dto
. As the context of theDomain Dto
layer would encompass any module, this dependency would apply to any module.Infrastructure Http
depends only onInfrastructure Http
andInfrastructure Service
but only within the context of its module.Infrastructure Service
depends only onApplication Service
andDomain Dto
, which is also influenced by the public exposure of these layers across any module.Designating a layer as a module is achieved by adding a "Named capturing group." The
public: true
option for unmodularized layers would not affect any other tool behavior. Thus, backward compatibility is preserved.