FREE · TOPIC 157

Logical System Design

692 words·Updated 2026-07-18·
#system-design#software-architecture#maintainability
# Logical System Design Logical system design is the structure of the application itself: where business rules live, which modules own which decisions, how data access is hidden, and how easily the system can absorb product change. It is different from [[wiki/system-design-archive-roadmap|architectural design]]. Architecture asks "which components exist?" Logical design asks **"where should this rule live so changing it later does not damage the rest of the system?"** ## Why It Matters Most production pain comes from change. Products pivot, pricing changes, providers fail, schemas evolve, and teams split ownership. If business logic is mixed directly with database queries, vendor SDK calls, and HTTP handlers, every change becomes a multi-file risk. ```mermaid flowchart TD Bad[Controller with business rules + SQL + provider SDK] --> Pain[High-change surface] Good[Controller] --> Service[Service / use case] Service --> Repo[Repository / DAO] Service --> Provider[Provider interface] Repo --> DB[(Database)] Provider --> External[External service] ``` ## What It Decides Logical design answers questions that are too code-level for architecture diagrams but too important to leave to accident. | Decision | Good default | Why it matters | |---|---|---| | Rule ownership | Put domain rules in use cases or domain services | Rules stay testable and visible | | Data access | Hide queries behind repositories or query objects | Storage changes do not leak everywhere | | Provider access | Wrap volatile vendors behind small interfaces | Replacements become localized | | Validation | Separate transport validation from business validation | HTTP shape and product rules can evolve independently | | Side effects | Make emails, payments, indexing, and webhooks explicit | Retries and failure handling become designable | ## Layering Is A Tool Layers are useful when each layer owns a different reason to change. A route handler changes when the API contract changes. A use case changes when the product rule changes. A repository changes when storage changes. Do not create layers just to satisfy a diagram. If a layer only forwards arguments without protecting a rule, dependency, or interface, it may be ceremony. ## Concrete Example In checkout, the controller should parse the request and authenticate the user. The checkout use case should decide whether the cart is purchasable, calculate totals, reserve inventory, and request payment. The payment provider should not decide whether a coupon is valid. The database query should not decide whether a user can buy a restricted item. Those are product decisions, so they belong near the domain flow. ## Boundary Checklist - Can the rule be tested without booting the web server? - Can the storage schema change without rewriting the business rule? - Can a failed provider call be retried or compensated deliberately? - Can a new team understand who owns the decision? - Can the core flow run with fake repositories and fake providers? ## Good Signs - Business rules are readable without understanding storage syntax. - Tests can exercise core behavior without a real database or third-party API. - Provider changes are localized behind interfaces. - Module boundaries follow domain concepts, not framework folders only. - The system can tolerate a product pivot without a ground-up rewrite. ## Failure Modes - One route handler performs validation, authorization, SQL, payment calls, email sending, and response formatting. - Every feature needs edits in many unrelated files. - Changing a database or external service requires auditing business logic line by line. - The data model leaks into every layer of the codebase. - A generic abstraction hides the real domain language and makes debugging harder. - Background jobs reimplement business rules differently from synchronous requests. ## Judgment Do not abstract everything. Abstract where volatility is likely or blast radius is high: payments, auth, storage providers, search indexing, notification providers, and complex domain rules. Start from the change you expect. If the risk is "we may switch email vendors", wrap the email vendor. If the risk is "pricing will change every month", isolate pricing rules and build direct tests for them. **Pro tip:** the senior move is often a narrow boundary, not a generic framework. "Put payment provider calls behind an interface" is usually better than "create an enterprise architecture layer."
Primary References & Engineering Sources
  • ·[[wiki/arpit-foundational-topics-system-design-i]]
  • ·[[Granola Notes - Foundational Topics in System Design I]]