ArchUnitNET icon indicating copy to clipboard operation
ArchUnitNET copied to clipboard

feat: Add HaveAnyParameters and NotHaveAnyParameters

Open Redestros opened this issue 5 months ago • 3 comments

Description

This PR adds support for checking if classes have private parameterless constructors in ArchUnitNET. This feature is particularly valuable for enforcing Domain-Driven Design patterns where entities should have private parameterless constructors for ORM frameworks while maintaining public constructors with parameters for domain logic.

Motivation

In Domain-Driven Design and Entity Framework scenarios, it's common to require:

  • Private parameterless constructors for ORM frameworks (Entity Framework, NHibernate, etc.)
  • Public parameterized constructors for domain logic and business rules
  • Abstract classes excluded from constructor requirements

This pattern ensures that:

  • ORM frameworks can instantiate entities during deserialization
  • Domain logic enforces proper entity creation through meaningful constructors
  • Encapsulation and invariants are maintained

Implementation

Core Changes

  • Added HavePrivateParameterlessConstructor() condition using SimpleCondition<Class>
  • Added NotHavePrivateParameterlessConstructor() condition for inverse validation
  • Integrated into fluent API via IClassConditions interface and ClassConditionsDefinition
  • Proper nullable handling for IsAbstract property (cls.IsAbstract == true)
  • Abstract class exclusion - automatically satisfy both conditions

Test Coverage

  • Comprehensive unit tests covering all scenarios
  • Edge case handling (abstract classes, parameterized-only constructors)

Usage Examples

Basic Usage

// Ensure domain entities have private parameterless constructors
var rule = Classes()
    .That().ResideInNamespace("MyApp.Domain.Entities")
    .And().AreNotAbstract()
    .Should().HavePrivateParameterlessConstructor()
    .Because("Domain entities need private constructors for ORM frameworks");

rule.Check(architecture);

Redestros avatar Sep 06 '25 23:09 Redestros

Hi @Redestros, I think this is too specific to be included as a separate fluent syntax method, because it basically combines a predicate and two conditions. I think a syntax like the following would be a better way to allow for this:

var classes = Classes()
    .That().ResideInNamespace("MyApp.Domain.Entities")
    .And().AreNotAbstract();
MethodMembers()
    .That()
    .AreDeclaredIn(classes)
    .And()
    .AreConstructors()
    .Should()
    .BePrivate()
    .AndShould()
    .NotHaveAnyParameters();

The only function that would be missing for this is NotHaveAnyParameters. I would be happy to accept a PR for adding HaveAnyParameters and NotHaveAnyParameters.

alexanderlinne avatar Sep 14 '25 09:09 alexanderlinne

Hi @alexanderlinne , Thank you for the feedback! You're absolutely right - the compositional approach is much more flexible. I've updated the PR accordingly. Changes Made I've removed the specific HavePrivateParameterlessConstructor condition and instead implemented the granular HaveAnyParameters and NotHaveAnyParameters conditions as you suggested. New Implementation:

✅ Added HaveAnyParameters() condition for MethodMembers ✅ Added NotHaveAnyParameters() condition for MethodMembers ✅ Added method signatures to IMethodMemberConditions interface ✅ Implemented in MethodMemberConditionsDefinition fluent API ✅ Added comprehensive test coverage

Usage Example Your suggested syntax now works perfectly:

var classes = Classes()
    .That().ResideInNamespace("MyApp.Domain.Entities")
    .And().AreNotAbstract();

MethodMembers()
    .That()
    .AreDeclaredIn(classes)
    .And()
    .AreConstructors()
    .Should()
    .BePrivate()
    .AndShould()
    .NotHaveAnyParameters();

Redestros avatar Sep 16 '25 20:09 Redestros

Codecov Report

:white_check_mark: All modified and coverable lines are covered by tests. :white_check_mark: Project coverage is 73.87%. Comparing base (cb83e98) to head (db5b3ca).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #403      +/-   ##
==========================================
+ Coverage   73.79%   73.87%   +0.08%     
==========================================
  Files         259      259              
  Lines       16387    16409      +22     
  Branches     1336     1336              
==========================================
+ Hits        12092    12122      +30     
+ Misses       3878     3870       -8     
  Partials      417      417              

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov-commenter avatar Sep 26 '25 06:09 codecov-commenter