Embedding business rules directly into database queries? That's a recipe for code you can't test. Imagine you're building an invoice search feature. You need to filter invoices by: - Invoice number - Customer name - Customer email - Order status - Product names All at once, with any combination of filters. The quick-and-dirty way? Jam everything straight into your query. With that implementation, the business rules just got trapped inside a database query. And you can't test that without a full database running. The alternative approach? Specification Pattern. It is a way to put complex query logic in a separate class, so that you can easily reuse and test it. Here's how it works: 1. You create a specification class that encapsulates your search criteria. 2. It takes your filters and builds the query logic. 3. Then you pass that specification to your repository or query handler. I've primarily used this pattern for various types of filtering. The result? Faster tests. Clearer business logic. More maintainable query code. The Specification Pattern is about putting your query logic in a place you can test, reuse, and understand six months from now.
How Developers Translate Business Rules Into Code
Explore top LinkedIn content from expert professionals.
Summary
Developers translate business rules into code by taking organizational policies or decisions and encoding them in a way that software can enforce and automate. This process involves structuring rules so they are easy to understand, test, and maintain as the business evolves.
- Centralize rule logic: Move business rules out of scattered code and into structured layers or systems so everyone can see and update them easily.
- Use expressive patterns: Adopt clear design patterns like specification classes or rich domain models to keep rules easy to test and adapt.
- Prevent invalid states: Encode valid configurations directly into data structures, making it impossible for software to end up in a broken or confusing state.
-
-
Make invalid states irrepresentable Imagine you're modeling a Form builder. You want to let your customers define a post-form behavior: either go to a "Thank you Page" hosted by the form builder saas (the one you're working on) or redirect to their own thank you page. You could model that like this: interface Form{ // ...other fields isRedirectEnabled: boolean; redirectLink? string; thankYouPage?: string; } This is a very beginner absolute garbage way to do it. Why? Because it requires that future maintainers keep business rules in their head, instead of business rules being encoded into the software itself. Consider all the invalid states that are allowed: Invalid Configuration example 1: isRedirectEnabled=false redirectLink=https://lnkd.in/ezSvjntg thankYouPage=null Invalid configuration example 2: isRedirectEnabled=true redirectLink=null thankYouPage=null Invalid configuration example 3: isRedirectEnabled=true redirectLink=null thankYouPage="Thanks for filling our form!" You could of course write a bunch of declarative validation code when instantiating these objects. But that's effortful and brittle. Consider this instead: interface Form{ // ...other fields postFormBehavior: PostFormBehavior, } type PostFormBehavior = RedirectBehavior | ThankYouPageBehavior; interface RedirectBehavior { type: 'redirect'; url: string; } interface ThankYouPageBehavior { type: 'thankYouPage'; message: string; } This creates a 𝘥𝘪𝘴𝘤𝘳𝘪𝘮𝘪𝘯𝘢𝘵𝘦𝘥 𝘶𝘯𝘪𝘰𝘯 that adds type safety and prevents any invalid configuration from ever being set. Now the valid configurations are encoded into the type system and typescript won't even let a future maintainer encode an invalid configuration. It's also extensible to allow more redirection types in the future. Any language with a decent type system allows you to do this stuff btw. Doesn't just apply to typescript.
-
Let’s say you’re building a SaaS app with a premium feature — say, exporting reports. When you hard-code entitlements, the logic might look like this inside your backend: if company.plan == 'pro' or company.plan == 'enterprise': allow_export = True else: allow_export = False Seems simple, right? But now a customer on the ‘starter’ plan negotiates with sales to get export access. So your developer hacks in a one-off override: if company.id == 12345: allow_export = True elif company.plan == 'pro' or company.plan == 'enterprise': allow_export = True else: allow_export = False Then Product adds a new tier. CS needs to enable exports temporarily for a few accounts. Marketing wants to run a limited-time trial. Pretty soon you’re scattering logic like this everywhere: if (company.plan in ['pro', 'enterprise', 'growth'] or company.id in [12345, 67890] or company.trial_flags.get('export') == True or company.custom_flags.get('export_enabled') == True): allow_export = True else: allow_export = False It’s brittle. It’s invisible. Nobody knows who has access to what, or why. Now multiply that by every feature you gate — usage limits, add-ons, API calls, roles, products — and suddenly your business logic is a landmine. Pricing experiments? Risky. Trials? Dangerous. Billing bugs? Guaranteed. A robust entitlements layer solves this by externalizing that logic into a structured, queryable system. Instead of coding it in, you check: if entitlements.check(company, 'can_export'): # proceed And that check pulls from a central config: the source of truth for what each company is entitled to — across plans, overrides, trials, whatever. Clean. Flexible. Safe.
-
Still dealing with anemic domain models? Let’s fix that. In many legacy C# codebases, you’ll find services that do everything: pricing, validation, stock checks. All while your entities are just data bags. It works... until it doesn’t. Imagine this instead: 1. Start by pushing one business rule into your domain class (stock check, discount logic, or credit limit). 2. Encapsulate internal collections and hide constructors to protect invariants. 3. Wrap domain operations behind expressive methods like SendInvitation(...) or Order.Create(...). 4. The application layer then becomes pure orchestration: load entity, call its behavior, save. With each small refactor, your domain becomes richer, tests cleaner, and code more resilient. No big rewrite needed. Want to see the full step-by-step refactor? Check out this article: https://lnkd.in/erhP4tNs Every pattern and tool has a purpose. It's up to you to understand when to use it. P.S. If you want a structure and detailed (multi-hour) guide to applying DDD in practice, I think you'll enjoy this: https://lnkd.in/eMyRkwcK
-
Ever had to explain refund logic buried in a Slack thread? Or re-implement a business rule that lived in someone’s memory? I built something: a decision layer A way to define business logic in YAML and actually treat it like code. You can: - Write policies (like refund rules) in YAML - Run them with real input - Test them - Trace exactly which rule fired and why - Version everything Why it matters: Policies shouldn’t be hidden in if statements You should know why a decision was made You should be able to test it before prod It’s all plain Python. Just logic you can read, run and trust. 🔗 GitHub: data-riot/decision-layer I’d love feedback. What’s missing? What’s confusing? What would make this actually useful for you?
-
In SAP projects, 𝐅𝐒 (𝐅𝐮𝐧𝐜𝐭𝐢𝐨𝐧𝐚𝐥 𝐒𝐩𝐞𝐜𝐢𝐟𝐢𝐜𝐚𝐭𝐢𝐨𝐧) 𝐚𝐧𝐝 𝐓𝐒 (𝐓𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐒𝐩𝐞𝐜𝐢𝐟𝐢𝐜𝐚𝐭𝐢𝐨𝐧) are not paperwork… they are the contract between business thinking and system behavior. Most project failures don’t happen because ABAP was hard. They happen because what was built ≠ what was expected. FS and TS exist to prevent that gap. 1️⃣ 𝐅𝐮𝐧𝐜𝐭𝐢𝐨𝐧𝐚𝐥 𝐒𝐩𝐞𝐜𝐢𝐟𝐢𝐜𝐚𝐭𝐢𝐨𝐧 (𝐅𝐒) : 𝐖𝐡𝐚𝐭 𝐭𝐡𝐞 𝐛𝐮𝐬𝐢𝐧𝐞𝐬𝐬 𝐰𝐚𝐧𝐭𝐬 Written by: Functional consultant / Business analyst Audience: Client, testers, developers This document translates business language → system requirement. It answers: ↳ What problem are we solving? ↳ What should the user be able to do? ↳ What fields should appear? ↳ What validations should happen? What output should come? 𝐓𝐡𝐢𝐧𝐤 𝐨𝐟 𝐅𝐒 𝐚𝐬: “𝐈𝐟 𝐭𝐡𝐞 𝐬𝐲𝐬𝐭𝐞𝐦 𝐛𝐞𝐡𝐚𝐯𝐞𝐬 𝐞𝐱𝐚𝐜𝐭𝐥𝐲 𝐥𝐢𝐤𝐞 𝐭𝐡𝐢𝐬, 𝐭𝐡𝐞 𝐛𝐮𝐬𝐢𝐧𝐞𝐬𝐬 𝐰𝐢𝐥𝐥 𝐛𝐞 𝐡𝐚𝐩𝐩𝐲.” 2️⃣ 𝐓𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐒𝐩𝐞𝐜𝐢𝐟𝐢𝐜𝐚𝐭𝐢𝐨𝐧 (𝐓𝐒) — 𝐇𝐨𝐰 𝐭𝐡𝐞 𝐬𝐲𝐬𝐭𝐞𝐦 𝐰𝐢𝐥𝐥 𝐝𝐨 𝐢𝐭 Written by: ABAP / Technical consultant Audience: Developer (and future developer who inherits the mess) This converts FS → build instructions. It answers: ↳ Which tables will be read? ↳ Which BADI/User Exit/Enhancement? ↳ Logic flow (step by step) ↳ Pseudocode ↳ Performance handling ↳ Error handling ↳ Transport objects 𝐓𝐡𝐢𝐧𝐤 𝐨𝐟 𝐓𝐒 𝐚𝐬: “𝐈𝐟 𝐭𝐡𝐞 𝐝𝐞𝐯𝐞𝐥𝐨𝐩𝐞𝐫 𝐜𝐨𝐝𝐞𝐬 𝐞𝐱𝐚𝐜𝐭𝐥𝐲 𝐭𝐡𝐢𝐬, 𝐭𝐡𝐞 𝐬𝐲𝐬𝐭𝐞𝐦 𝐰𝐢𝐥𝐥 𝐦𝐚𝐭𝐜𝐡 𝐅𝐒.” - SAP CDS and AMDP : https://lnkd.in/gRn6pkzu
-
Complex queries often spread across services, repositories, and controllers. Filtering rules, includes, and ordering logic get duplicated everywhere. ✅ 𝐓𝐡𝐚𝐭 𝐢𝐬 𝐞𝐱𝐚𝐜𝐭𝐥𝐲 𝐰𝐡𝐞𝐫𝐞 𝐭𝐡𝐞 𝐒𝐩𝐞𝐜𝐢𝐟𝐢𝐜𝐚𝐭𝐢𝐨𝐧 𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐬𝐡𝐢𝐧𝐞𝐬. In one of my projects, different services needed variations of the same query for 𝙰𝚛𝚝𝚒𝚌𝚕𝚎 entities. 1. Some endpoints needed only published articles. 2. Others required recent articles ordered by publish date. 3. Some required related data such as 𝙰𝚞𝚝𝚑𝚘𝚛 and 𝙲𝚘𝚖𝚖𝚎𝚗𝚝𝚜. ❌ The problem appeared quickly. LINQ queries started spreading across multiple services, each slightly different. ⚠️ The common approach is to keep adding filters directly inside services or repositories. That leads to duplicated logic, large methods, and tightly coupled query construction. A cleaner approach is using the Specification Pattern. Instead of embedding query rules in services, each rule becomes its own object. For example: • 𝙿𝚞𝚋𝚕𝚒𝚜𝚑𝚎𝚍𝙰𝚛𝚝𝚒𝚌𝚕𝚎𝚜𝚂𝚙𝚎𝚌𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 defines the filtering criteria • 𝚁𝚎𝚌𝚎𝚗𝚝𝚕𝚢𝙿𝚞𝚋𝚕𝚒𝚜𝚑𝚎𝚍𝚂𝚙𝚎𝚌𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 defines time based filtering and ordering • 𝙰𝚛𝚝𝚒𝚌𝚕𝚎𝚆𝚒𝚝𝚑𝙳𝚎𝚝𝚊𝚒𝚕𝚜𝚂𝚙𝚎𝚌𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗 defines navigation property includes Each specification inherits from a base 𝚂𝚙𝚎𝚌𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗<T> and defines rules such as 𝙲𝚛𝚒𝚝𝚎𝚛𝚒𝚊, 𝙸𝚗𝚌𝚕𝚞𝚍𝚎𝚜, and 𝙾𝚛𝚍𝚎𝚛𝙱𝚢. A small utility like 𝚂𝚙𝚎𝚌𝚒𝚏𝚒𝚌𝚊𝚝𝚒𝚘𝚗𝙴𝚟𝚊𝚕𝚞𝚊𝚝𝚘𝚛 applies these rules to an 𝙸𝚀𝚞𝚎𝚛𝚢𝚊𝚋𝚕𝚎<T>. EF Core translates the expressions directly to SQL, so the abstraction stays efficient. The benefits become clear as the system grows. Responsibilities stay separated: • Services orchestrate workflows • Specifications define query rules • EF Core executes the query Business rules live in one place and can be reused across APIs, background jobs, or reporting. P.S. The Specification Pattern keeps EF Core queries reusable, testable, and easier to evolve as complexity grows. ♻️ Share this with your network if this helped ➕ Follow me [ Elliot One ] + Enable Notifications 📌 Receive high-quality AI and systems engineering insights every Saturday with The Modern Engineer. 👉 Subscribe now at elliotone.com
Explore categories
- Hospitality & Tourism
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Healthcare
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Career
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development