interviewpath
  • 0day streak
  • 10total XP
  • Level 1

Focus mode

Data modeling, ownership and dependency boundaries

Read alongside

MIT 6.102 Software Construction (readings): 07: Abstraction Functions & Rep Invariants

Precise vocabulary for invariants, rep exposure and representation independence, with short exercises; read 06 (Abstract Data Types) first.

Objectives

  • Model a small domain with entities, value objects and relationships, and state the invariants each type guarantees.
  • Write a representation invariant and abstraction function for a data type and identify code that could expose or break the representation.
  • Decompose a system by the design decisions most likely to change and justify each module boundary with a concrete change scenario.
  • Assign ownership of each piece of data to one module or service and describe how other components read or change it.
  • Draw the dependency graph of a codebase and remove a cycle or a dependency from domain logic onto infrastructure.
  • Compare integrating two services through a shared database with integrating them through an API or events, in terms of coupling, consistency and change cost.

Why it matters

Code is read and changed far more often than it is written, so the main cost of a design is the cost of the next change. A model with clear owners and enforced invariants confines a change to one place; a model where three modules write the same table, or where business rules live in HTTP handlers, turns every change into an investigation. Object-oriented and low-level design rounds ask you to model a parking garage, a rate limiter or a document editor precisely to see how you choose types and boundaries, and senior interviews return to the same theme at service scale: who owns this data, and what happens when it has to change?

Core ideas

Types carry invariants. A good type makes its valid states explicit and keeps invalid ones unrepresentable. MIT's software construction readings name the two halves: a representation invariant says which internal states are legal, and an abstraction function says what a legal state means. Establish the invariant in the constructor, preserve it in every operation, and never hand out references to mutable internals; that leak, called rep exposure, lets callers break the invariant from outside. Prefer immutable value objects for concepts defined by their value (money, a date range, an email address) and entities with stable identity for things that change over time (an account, an order).

Decompose by what changes. Parnas's 1972 paper argues that each module should hide a design decision likely to change, such as how data is represented or which algorithm is used, rather than mirror a step of a processing flowchart. The same reasoning applies today to vendors, model providers and other external services. A boundary is good if a plausible change touches one module. High cohesion (the parts of a module change together) and low coupling (modules change independently) are the observable results.

Ownership. Every piece of data should have one writer that enforces its invariants. Other components read through that owner's interface, through events it publishes, or through a read replica, and accept the staleness that implies. A database written by several services feels convenient until the schema must change and nobody knows who depends on which column. Inside a monolith the same rule applies to modules and tables.

Dependency direction. Domain logic should not import the web framework, the ORM or a vendor SDK. Put those behind narrow interfaces (ports) implemented by adapters at the edge; the core then runs and is tested with in-memory fakes, and infrastructure can change without touching business rules. Dependencies should point from volatile code toward stable abstractions, and a cycle between packages usually means a boundary is in the wrong place.

Bounded contexts. Large domains rarely share one consistent model: "contact" can mean different things to sales, billing and compliance. Domain-driven design calls each internally consistent region a bounded context and makes translation between contexts explicit instead of forcing one universal schema.

A small illustration: in a multi-tenant outreach tool, a Campaign type could own its status transitions (draft, scheduled, sending, finished) and expose schedule(at) and cancel() instead of a settable status field, so no caller can move a finished campaign back to draft. Messages reference the campaign by ID and are written only by the sending component, which owns delivery state.

Common misconceptions

  • "Good modeling means deep inheritance hierarchies." Maintainable designs mostly use composition and small interfaces; inheritance couples a subclass to its parent's implementation.
  • "Getters and setters provide encapsulation." A public setter for every field exposes the representation; encapsulation means offering operations that preserve invariants.
  • "Microservices create good boundaries." A network boundary in the wrong place produces a distributed monolith with extra failure modes; get module boundaries right first.
  • "Always normalize" or "always denormalize." Normalization protects invariants at write time and denormalization serves read paths; decide per access pattern and name the owner of every copy.
  • "Never duplicate code." Two fragments that look alike but change for different reasons should stay separate; merging them couples unrelated changes.

How to approach it

Read the MIT 6.102 readings on abstract data types and on abstraction functions and rep invariants, then Parnas's short paper. Practice by modeling three domains on paper in 30 minutes each: write the types, their invariants, the operations that change state and the owner of each table. In interviews, start from use cases and invariants rather than class diagrams, say which component owns which data, and show how the design absorbs a specific change ("adding a second sending channel only adds an adapter"). Graduate answers should be precise about types and invariants; mid-level answers should separate I/O from logic; senior answers should argue ownership and boundaries across teams and plan how the model will evolve.

Self-check before you finish

  1. What is rep exposure, and how do immutability or defensive copying prevent it?
  2. When should a concept be a value object rather than an entity?
  3. What goes wrong when two services write the same table?
  4. Which way should dependencies point between domain logic and the database layer, and why?
  5. What is a bounded context, and when would you translate between two models instead of merging them?
  6. How can you tell that a module boundary is in the wrong place?