Loading learning content...
In the vast landscape of a software system — with its databases, API endpoints, message queues, and user interfaces — there exists a core that defines what the system actually does. This core contains the policies, rules, and business logic that give the system its purpose and value. Without this core, you just have plumbing; with it, you have a solution to a real-world problem.
This core is what we call the high-level layer of your system. Understanding what constitutes a high-level module — and more importantly, why it deserves special architectural attention — is fundamental to applying the Dependency Inversion Principle effectively.
By the end of this page, you will understand what makes a module 'high-level,' recognize policy and business logic in real codebases, distinguish between core domain logic and peripheral concerns, and appreciate why high-level modules are the most valuable — and most vulnerable — parts of your architecture.
A high-level module is a component that encapsulates the essential purpose of your system — the business rules, policies, and domain logic that justify the system's existence. If you stripped away all the databases, frameworks, and infrastructure, the high-level logic would still represent what your organization needs to accomplish.
Consider a company's expense reimbursement policy:
"Employees may claim expenses up to $500 without manager approval. Expenses above $500 require manager approval. Expenses above $5,000 require VP approval and a business justification."
This is policy — a set of business rules that define how the organization operates. These rules exist independently of:
The policy is the what; everything else is the how.
To identify high-level logic, ask: 'If I described what this code does to a business stakeholder who knows nothing about programming, would they care?' If yes — that's high-level. If your description involves databases, HTTP, or specific technologies, you're probably describing low-level concerns.
High-level modules share several defining characteristics that distinguish them from their low-level counterparts:
| Characteristic | Description | Example |
|---|---|---|
| Business Vocabulary | Uses language from the problem domain, not technical domain | ProcessLoanApplication, CalculateShippingCost, ApproveExpense |
| Defines 'What' Not 'How' | Specifies outcomes and behaviors, not mechanisms | "Orders over $100 get free shipping" vs "INSERT INTO orders..." |
| Technology-Agnostic | Can be described without mentioning specific technologies | Pricing logic works whether data comes from SQL, NoSQL, or in-memory cache |
| Stable Over Time | Changes when business requirements change, not when infrastructure changes | Tax calculation rules change with legislation, not with database migrations |
| High Business Value | Directly contributes to the organization's competitive advantage or core mission | A bank's fraud detection algorithms vs. its logging infrastructure |
Business logic is the encoded representation of the rules, workflows, and decisions that a business makes. It's the computational embodiment of organizational knowledge. Let's examine the layers and forms that business logic takes.
Business logic isn't monolithic — it manifests in several distinct categories, each with its own characteristics:
Let's examine how business logic manifests in a real e-commerce pricing scenario. Consider the following pricing rules:
Here's how this business logic might be expressed in code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
/** * PricingPolicy — A high-level module containing business pricing rules. * * Notice: * - No mention of databases, HTTP, or file systems * - Uses business vocabulary (Member, Product, Promotion) * - Rules are explicit and readable by domain experts * - Dependencies are abstractions, not concrete implementations */interface Product { id: string; name: string; basePrice: Money; category: ProductCategory;} interface PricingContext { product: Product; quantity: number; customer: Customer; activePromotions: Promotion[]; pricingDate: Date;} interface PricingResult { originalPrice: Money; finalPrice: Money; appliedDiscounts: AppliedDiscount[]; savingsBreakdown: SavingsBreakdown;} class PricingPolicy { private static readonly MEMBER_DISCOUNT_PERCENT = 10; private static readonly BULK_ORDER_THRESHOLD = 10; private static readonly BULK_DISCOUNT_PERCENT = 5; private static readonly MAX_TOTAL_DISCOUNT_PERCENT = 30; /** * Calculate the final price for a purchase. * * This is pure business logic — it takes a context and returns a result. * No I/O, no side effects, no infrastructure concerns. */ calculatePrice(context: PricingContext): PricingResult { const originalPrice = context.product.basePrice.multiply(context.quantity); const discounts: AppliedDiscount[] = []; // Rule 1: Member discount if (context.customer.isMember()) { discounts.push({ type: 'MEMBER_DISCOUNT', description: 'Member exclusive: 10% off', percent: PricingPolicy.MEMBER_DISCOUNT_PERCENT, }); } // Rule 2: Bulk order discount if (context.quantity >= PricingPolicy.BULK_ORDER_THRESHOLD) { discounts.push({ type: 'BULK_DISCOUNT', description: `Bulk order discount for ${context.quantity}+ items`, percent: PricingPolicy.BULK_DISCOUNT_PERCENT, }); } // Rule 3: Promotional discounts const applicablePromotions = this.findApplicablePromotions( context.product, context.activePromotions, context.pricingDate ); for (const promo of applicablePromotions) { discounts.push({ type: 'PROMOTIONAL', description: promo.description, percent: promo.discountPercent, }); } // Rule 4: Cap total discount at maximum const totalDiscountPercent = this.calculateCappedDiscount(discounts); const finalPrice = originalPrice.applyDiscount(totalDiscountPercent); return { originalPrice, finalPrice, appliedDiscounts: discounts, savingsBreakdown: this.buildSavingsBreakdown(originalPrice, finalPrice, discounts), }; } private calculateCappedDiscount(discounts: AppliedDiscount[]): number { const rawTotal = discounts.reduce((sum, d) => sum + d.percent, 0); return Math.min(rawTotal, PricingPolicy.MAX_TOTAL_DISCOUNT_PERCENT); } private findApplicablePromotions( product: Product, promotions: Promotion[], date: Date ): Promotion[] { return promotions.filter(promo => promo.isActiveOn(date) && promo.appliesTo(product) ); } private buildSavingsBreakdown( original: Money, final: Money, discounts: AppliedDiscount[] ): SavingsBreakdown { // Build detailed breakdown for display purposes // Pure calculation, no side effects return { totalSaved: original.subtract(final), percentSaved: original.subtract(final).divideBy(original).toPercent(), discountsApplied: discounts.length, }; }}Notice what's absent from this high-level module: No SQL queries, no HTTP calls, no file system access, no framework annotations, no configuration loading. It's pure business logic. You could test it without any infrastructure, explain it to a product manager, and port it to any technology stack without change.
High-level modules aren't flat — they form layers of abstraction, with higher layers representing broader, more strategic policies and lower layers representing more specific, tactical decisions. Understanding this hierarchy is crucial for proper system architecture.
Robert C. Martin (Uncle Bob), who formalized the Dependency Inversion Principle, describes software as layers of policies arranged by their level of abstraction:
Each layer serves a distinct purpose and has different stability characteristics:
| Layer | Stability | Change Drivers | Example Rules |
|---|---|---|---|
| Enterprise Policy | Most stable (years) | Regulatory changes, organizational restructuring | "All financial transactions must be auditable", "Customer PII must be encrypted" |
| Application Policy | Stable (months) | New features, product pivots | "Premium users can export reports", "Orders ship within 2 business days" |
| Domain Layer | Fairly stable (months) | Domain model evolution, new entity types | "An Order contains LineItems", "A Subscription has a renewal date" |
| Interface Adapters | Moderate (weeks) | API changes, new UI requirements | "REST endpoints return JSON", "GraphQL resolvers map to domain objects" |
| Infrastructure | Least stable (days/weeks) | Technology upgrades, scaling needs, vendor changes | "Use PostgreSQL 15", "Deploy to AWS Lambda" |
Many systems get this inverted. They make high-level business logic depend on low-level infrastructure — the exact opposite of what they should do. When the database schema changes, the business rules break. When the framework upgrades, the domain logic needs rewriting. This is the disease that DIP cures.
A key insight from this hierarchy is that policies at higher levels should be expressible and executable independently of lower levels. Your enterprise security policy shouldn't mention PostgreSQL. Your pricing logic shouldn't contain HTTP status codes. Your domain model shouldn't know about JSON serialization.
This independence isn't just an aesthetic preference — it's a structural necessity for maintainability:
Let's examine how high-level modules manifest across different domains. These examples illustrate the universal pattern: business-critical logic that defines what the system does, decoupled from how it does it.
A banking system's high-level modules encode the fundamental rules of financial transactions:
1234567891011121314151617181920212223242526272829303132333435363738394041424344
/** * TransferPolicy — High-level banking business rules */class TransferPolicy { // Business rule: transfer limits based on account type and verification private readonly DAILY_LIMITS: Record<AccountTier, Money> = { BASIC: Money.of(1000, 'USD'), VERIFIED: Money.of(10000, 'USD'), PREMIUM: Money.of(100000, 'USD'), }; validateTransfer(transfer: TransferRequest): ValidationResult { const errors: PolicyViolation[] = []; // Rule: Insufficient funds (core banking invariant) if (transfer.sourceAccount.availableBalance.isLessThan(transfer.amount)) { errors.push(PolicyViolation.insufficientFunds( transfer.amount, transfer.sourceAccount.availableBalance )); } // Rule: Daily limit enforcement const dailyTotal = transfer.sourceAccount.todaysTransferTotal.add(transfer.amount); const limit = this.DAILY_LIMITS[transfer.sourceAccount.tier]; if (dailyTotal.isGreaterThan(limit)) { errors.push(PolicyViolation.dailyLimitExceeded(dailyTotal, limit)); } // Rule: Account status check if (transfer.sourceAccount.isFrozen || transfer.targetAccount.isFrozen) { errors.push(PolicyViolation.accountFrozen()); } // Rule: Same-account transfer prohibition if (transfer.sourceAccount.equals(transfer.targetAccount)) { errors.push(PolicyViolation.sameAccountTransfer()); } return errors.length === 0 ? ValidationResult.success() : ValidationResult.failed(errors); }}Notice the business vocabulary: 'insufficient funds', 'daily limit', 'account frozen'. These are concepts a bank compliance officer would recognize and care about. The policy doesn't know if accounts are in PostgreSQL or an external banking API.
How do you recognize high-level concerns when reading or writing code? Here are practical techniques for identifying policy and business logic:
For each piece of code, ask whether a non-technical business stakeholder would care about what it does:
Imagine your company decides to migrate from PostgreSQL to MongoDB, or from REST to GraphQL, or from AWS to Azure. Which parts of your code would survive unchanged?
If testing a piece of code requires mocking databases, HTTP clients, file systems, or message queues, that code is probably entangled with low-level concerns. Pure high-level logic should be testable with simple unit tests using plain objects.
When you find high-level logic mixed with low-level details, mentally (or actually) extract the business rules into their own pure functions or classes. If what remains is just I/O glue code, you've found the boundary. The extracted portion is your high-level module.
Understanding why high-level modules are valuable clarifies why the Dependency Inversion Principle is so important.
High-level modules represent your organization's core intellectual property in code form. Consider:
Given their value, high-level modules deserve protection from unnecessary change. Every time infrastructure details leak into business logic:
The Dependency Inversion Principle is the primary tool for achieving this protection. Rather than having high-level policy depend on low-level details, both should depend on abstractions, with the abstractions "owned" by the high-level layer.
High-level modules should be the most stable, most tested, and most protected parts of your codebase. They change when the business changes, not when the technology changes. Every dependency flowing into them is a potential source of instability. DIP ensures those dependencies point the right direction.
We've established a comprehensive understanding of what constitutes high-level modules and why they matter:
Coming Up Next:
Now that we understand high-level modules, the next page examines their counterpart: low-level modules and implementation details. We'll explore what makes something 'low-level,' why these modules are inherently more volatile, and how to recognize the boundary between policy and mechanism in your code.
You now have a deep understanding of high-level modules as carriers of business policy and organizational value. This foundation is essential for understanding why the Dependency Inversion Principle advocates for protecting these modules through proper dependency direction.