The Rule Pattern in C#, and Why It Works So Well with the Result Pattern
Business rules rarely stay in one place.
A small validation starts in a command handler. Another check appears in a service. A third gets added somewhere in the domain. A few weeks later, the real business policy is scattered across the application, and nobody can see it clearly anymore.
That is exactly where the Rule pattern becomes useful.
At its core, the idea is simple. Instead of expressing business decisions as anonymous conditionals spread throughout the system, you give each rule a name and wrap it in its own object. Martin Fowler describes this style through the Specification pattern, where the statement of what satisfies a candidate is separated from the candidate itself, and those specifications can be combined into richer logic. (martinfowler.com)
In practical C# applications, especially the ones using CQRS or domain-driven design, that gives you something powerful: business rules that are explicit, reusable, and easy to reason about. (martinfowler.com)
What the Rule pattern really solves
The biggest problem is not that business checks exist. The problem is that they often exist in the wrong shape.
When rules are buried inside handlers, services, or controller actions, they are hard to spot, hard to test in isolation, and very easy to duplicate. The code still works, but the business language disappears.
The Rule pattern fixes that by turning each business condition into a named concept. Instead of asking developers to interpret a chain of low-level checks, the code starts speaking in terms the business would actually recognize. That is very much in line with DDD’s emphasis on modeling around the real language of the domain. (Microsoft Learn)
In other words, the pattern is not just about validation. It is about clarity.
Why it is often called the Specification pattern
If the shape feels familiar, that is because it usually is. What many teams call a Rule pattern is very close to what Fowler documented as Specification. The core idea is to represent business criteria as composable objects rather than raw conditionals, and then combine them with logical operations like AND, OR, and NOT. (martinfowler.com)
That is why this pattern tends to feel so natural in real systems. Business rules are rarely isolated. More often, they are layered:
A customer must be verified.
An order must reach a minimum amount.
A credit limit must not be exceeded.
On their own, each rule is simple. The real value appears when they can be combined into a meaningful policy.
Why this pattern becomes more valuable as the system grows
At first, a few inline checks seem harmless. But as the application grows, the cost of hidden business logic grows with it.
Rules get copied into multiple workflows.
One endpoint enforces them, another forgets.
A handler applies three checks, a background job applies only two.
Tests start focusing on branches instead of policies.
When rules become standalone concepts, those problems start to shrink. You can test a rule directly. You can reuse it in multiple flows. You can combine it with other rules. Most importantly, you can read the application flow and understand what it is trying to protect.
That is one of the reasons this pattern fits so well in systems that care about maintainability.
Where the Result pattern fits
The Rule pattern answers one question:
Is this action allowed according to the business?
The Result pattern answers another:
What happened when we tried to perform it?
That separation is the reason the two patterns fit together so well.
The rule evaluates business truth.
The result communicates application outcome.
Vladimir Khorikov’s writing on Result-based error handling makes this distinction especially useful for expected failures. Instead of throwing exceptions for normal business rejections, you can return an explicit failure outcome and keep the flow predictable. (Enterprise Craftsmanship)
That gives you a very clean mental model:
The domain decides.
The application reports.
Why this pairing feels so clean in practice
A good rule object should not care about HTTP, API responses, logging, UI wording, or transport concerns. Its responsibility is much smaller. It answers whether a business condition holds.
The application layer then takes that answer and decides what to do next. If the rule fails, the application can return a failure result. If it passes, the use case continues.
This separation lines up well with Microsoft’s DDD guidance, which treats validations as invariants and emphasizes that aggregates are responsible for enforcing consistency across state changes. (Microsoft Learn)
That is why the combination works so well. The rule keeps business logic clean. The result keeps flow control clean.
A simple order example, without the code
Imagine an order placement use case with three policies:
The order must meet a minimum amount.
The customer must be verified.
The order must stay within the customer’s credit limit.
Without a rule-based approach, those checks often end up inline inside the command handler. That works, but the handler slowly becomes the place where business policy lives.
With a rule-based approach, those policies become named rules. The handler simply coordinates the workflow: load data, build the order, evaluate the rule set, return a success or failure result, and persist when everything is valid.
That is a much healthier separation of concerns.
The handler stops being the place where business logic is invented, and becomes the place where business logic is orchestrated.
The biggest benefit is not flexibility, it is readability
People often talk about this pattern in terms of reuse or composability, and those are real benefits.
But the biggest benefit is readability.
When rules are named well, the code starts reading like a business conversation instead of a technical negotiation. That matters more than people think. Clear code reduces mistakes, speeds up maintenance, and makes onboarding easier for everyone who touches the system later.
A long chain of conditionals tells you how the system behaves.
A well-named rule tells you why.
One important design note
There is one trap worth mentioning.
Just because a rule is expressed cleanly does not automatically mean it belongs in the application layer.
Some rules are just use-case checks. Those can live comfortably near the workflow.
But some rules are true invariants of the domain. Those should usually be protected by the aggregate itself. Microsoft’s guidance is very clear on this point: aggregates are responsible for enforcing invariants across state changes. (Microsoft Learn)
So the right question is not only, “Can I model this as a rule?”
The better question is, “Who is responsible for protecting this rule?”
If the answer is “the domain itself,” then the aggregate should not rely on the handler to remember it.
Where this pattern can go wrong
Like most good patterns, this one becomes dangerous when overused.
Not every if statement needs to become a rule object.
Not every workflow needs an entire rules engine.
Not every tiny validation deserves its own abstraction.
If the business logic is trivial and local, introducing rule classes may create more ceremony than value.
The pattern shines when the rules are meaningful, reusable, and important enough to deserve a name. If the condition is just a one-off technical check, a plain conditional may still be the better choice.
The goal is not abstraction for its own sake. The goal is to make business intent easier to see.
Why the Result pattern keeps the design honest
The Result pattern adds a second layer of clarity.
A failure is no longer hidden behind a thrown exception for something the system fully expected. Instead, the caller gets an explicit outcome. That makes business rejections feel normal rather than exceptional, which is often the right model for application workflows. (Enterprise Craftsmanship)
When paired with rules, the flow becomes very natural:
Evaluate the business policy.
If it fails, return a failure result.
If it passes, continue the operation.
That is simple, predictable, and easy to test.
Final thought
The Rule pattern is one of those ideas that looks modest at first, but pays off more and more as the codebase grows.
It helps you name business policies.
It helps you reuse them.
It helps you combine them.
And when paired with the Result pattern, it gives your application a very clean way to both enforce rules and communicate outcomes. (martinfowler.com)
If your handlers are starting to fill up with business conditionals, this pattern is often a strong sign that the code wants a better language.
And that, more than anything else, is what good domain design is really about.
Source code :
https://github.com/stevsharp/RulePatternApplication
References
Martin Fowler, Specifications. (martinfowler.com)
Microsoft Learn, Designing validations in the domain model layer. (Microsoft Learn)
Microsoft Learn, Designing a DDD-oriented microservice. (Microsoft Learn)
Vladimir Khorikov, Error handling: Exception or Result? (Enterprise Craftsmanship)
Vladimir Khorikov, Advanced error handling techniques. (Enterprise Craftsmanship)