Skip to content

Conversation

@Redestros
Copy link

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 Redestros force-pushed the feature/private-parameterless-constructor branch from 0b2bb6a to e64d5f7 Compare September 6, 2025 23:37
@alexanderlinne
Copy link
Collaborator

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 alexanderlinne self-assigned this Sep 14, 2025
@Redestros
Copy link
Author

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 Redestros changed the title feat: Add HavePrivateParameterlessConstructor and NotHavePrivateParameterlessConstructor feat: Add HaveAnyParameters and NotHaveAnyParameters Sep 16, 2025
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.46%. Comparing base (929d0cb) to head (dca010b).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #403      +/-   ##
==========================================
+ Coverage   77.39%   77.46%   +0.06%     
==========================================
  Files         257      257              
  Lines       19034    19056      +22     
  Branches     1592     1592              
==========================================
+ Hits        14731    14761      +30     
+ Misses       3848     3840       -8     
  Partials      455      455              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

@Redestros Redestros force-pushed the feature/private-parameterless-constructor branch 2 times, most recently from 1b1890f to fd7746f Compare September 26, 2025 19:48
…eterlessConstructor conditions

Add support for checking if classes have private parameterless constructors,
which is essential for enforcing Domain-Driven Design patterns and ORM requirements.

Signed-off-by: Ahmed Mohamed El Ahmar <ahmedmohamedelahmar@gmail.com>
…embers

- Add HaveAnyParameters() condition to check if methods have parameters
- Add NotHaveAnyParameters() condition to check if methods have no parameters
- Add corresponding methods to IMethodMemberConditions interface
- Add comprehensive test coverage for both conditions
- Test both regular methods and constructors
- Verify failure descriptions in violation cases
- Use correct IL method names (.ctor) for constructor tests
- Remove redundant test cases that provided no additional value

The new conditions enable architectural rules to enforce parameter
requirements on methods and constructors, complementing existing
method member conditions.

Signed-off-by: Ahmed Mohamed El Ahmar <ahmedmohamedelahmar@gmail.com>
@Redestros Redestros force-pushed the feature/private-parameterless-constructor branch from fd7746f to 4cd515f Compare October 2, 2025 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants