Logical system design: modules and boundaries

Split a URL shortener into HTTP, link-rule and storage responsibilities, give each boundary explicit results, and test product rules without a database.

Logical system design

Logical system design decides where application rules live and how modules work together. It helps answer a practical question: when a product rule or external provider changes, which code should need to change with it?

We will separate the shortener's HTTP handling, link rules, and storage work, then check whether those boundaries make a change easier to test.

Give each responsibility an owner

A route handler can parse a request, run SQL, check whether a link is disabled, record a click, and format the redirect. For a small operation, that can be readable. Trouble starts when an import job needs the same rules, or every handler begins interpreting database rows differently.

Separate the work when those responsibilities need to vary independently:

Responsibility What it owns --- --- HTTP adapter Parse the request and translate the result into an HTTP response Link operation Apply the product's rules for creating or opening a link Storage adapter Execute queries and map stored records into the application's result types

A boundary is the agreement between these parts. It can be an ordinary function call inside one process. Drawing separate boxes does not require separate servers or deployments.

The HTTP path creates one link; the import job may create many. Both must enforce the same rule that a generated code cannot replace an existing mapping. The database's uniqueness constraint enforces the collision rule atomically; the application decides whether to retry with another candidate or return an error.

Make the agreement useful to its caller

Consider resolve(code) . “Returns a URL” leaves several cases unstated. The link might be absent, disabled, or temporarily impossible to look up.

Result Meaning HTTP caller's action --- --- --- Found The operation permits this link and supplies its destination Continue the redirect path Missing The lookup succeeded, but no mapping exists Return not found Disabled The mapping exists but cannot be used Refuse the redirect Unavailable The operation could not establish an answer Return a service error

Keep unavailable distinct from missing. Translating a connection failure into “no such link” gives the caller a false answer. Likewise, the storage adapter should not leak a vendor-specific exception into every route and job that uses it.

For the shortener's agreed contract, a successful redirect also waits for its request event to be stored. That sequencing belongs to the link operation. HTTP formatting does not decide whether losing a click is acceptable.

Test the rule through the boundary

To test “disabled links never redirect,” supply a small in-memory storage implementation that returns a disabled record. Exercise the link operation directly. The test should not need a browser, a live database, or knowledge of SQL syntax to check that product rule.