Skip to content

Proposal for clearer rule definition syntax with canBeUsedBy #491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pfazzi opened this issue May 1, 2025 · 6 comments
Open

Proposal for clearer rule definition syntax with canBeUsedBy #491

pfazzi opened this issue May 1, 2025 · 6 comments
Labels
enhancement New feature or request

Comments

@pfazzi
Copy link
Collaborator

pfazzi commented May 1, 2025

Feature Request

Q A
New Feature yes
RFC yes
BC Break no

Summary

I have a recurring use case in our codebase where we want to prevent all modules from depending on a specific namespace, except a couple of well-known exceptions.

Today, this is expressed like this:

// No other module should depend on this one.
// We only allow two known exceptions: Logging and RequestHandler.
Rule::allClasses()
    ->except(
        'Acme\Quoting\Requests*',
        'Acme\Domain\Logging',
        'Acme\Service\RequestHandler',
    )
    ->that(new ResideInOneOfTheseNamespaces('Acme'))
    ->should(new NotDependsOnTheseNamespaces('Acme\Quoting\Requests'))
    ->because('no other module except Logging and RequestHandler should depend on the Requests module');

I think this rule is harder to read and easy to get wrong, especially as the number of exceptions grows.

I’d like to propose a second, more declarative syntax to make this kind of intent more readable:

Rule::namespace('Acme\Quoting\Requests')
    ->canBeUsedBy(
        'Acme\Domain\Logging',
        'Acme\Service\RequestHandler'
    );

Thanks!

@fain182
Copy link
Collaborator

fain182 commented May 1, 2025

I see that the declarative syntax is more readable, but I think it would be difficult to explain when to use Rule::allClasses()->that(new ResideInOneOfTheseNamespaces('Acme')) and when to use Rule::namespace('Acme')...

Maybe we can improve the situation making something like:

Rule::allClasses()
    ->that(new ResideInOneOfTheseNamespaces('Acme\Quoting\Requests'))
    ->should(new BeUsedOnlyBy(['Acme\Domain\Logging', 'Acme\Service\RequestHandler']));

Or some other ways to fit this new concept in our existing structure..
(I think the implementation of this could be tricky, maybe someone else has better ideas.)

@fain182
Copy link
Collaborator

fain182 commented May 1, 2025

Now that I've read #492, I wonder if Rule::namespace('Acme') might just be a shortcut for Rule::allClasses()->that(new ResideInOneOfTheseNamespaces('Acme'))...

@pfazzi
Copy link
Collaborator Author

pfazzi commented May 1, 2025

You’re right, but my goal here is to offer a simpler and more expressive DSL to cover the most common architectural rules we encounter in real-world projects.

For example, with this kind of DSL:

Rule::namespace('Acme\Quoting\RequestsForQuote')
    ->shouldNotDependOnOtherNamespaces()
    ->except('Acme\Entity\Quote', 'Psr', 'Symfony', 'RdKafka', 'Assert', 'Redis');

Rule::namespace('Acme\Quoting\RequestsForQuote')
    ->canOnlyBeUsedBy(
        'Acme\Domain\Logging',
        'Acme\Service\RequestHandler',
    );

…I can express most of the rules in our codebase in a very concise and readable way. The intention becomes crystal clear, and the developer experience improves a lot from my POV, especially for engineers who don’t want to dive into the Arkitect APIs every time.

I’d love to evolve this DSL further if there’s interest.

@AlessandroMinoccheri
Copy link
Member

in my opinion we sould try to foloow this idea, it can add more flexibility and options for developers at the end.

@pfazzi
Copy link
Collaborator Author

pfazzi commented May 2, 2025

I think we can probably keep the current DSL and build on top of it a more high-level DSL that covers the most relevant cases.

@pfazzi
Copy link
Collaborator Author

pfazzi commented May 2, 2025

An even simpler version of the DSL:

Rule::namespace('Acme\Compliance')
    ->canDependOnlyOn('Acme\Common', 'Acme\Clock');

Rule::namespace('Acme\Compliance')
    ->shouldNotBeUsedByAnyOtherModule();

Rule::namespace('Acme\Pricing')
    ->canOnlyBeUsedBy('Acme\Logging', 'Acme\Service\RequestHandler');

@micheleorselli micheleorselli added the enhancement New feature or request label May 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants