WEB / WEB-16.04
Service layer, business logic, and templates
A public note domain result and its HTTP response are different decisions. Follow ownership across controller, service, repository, and template.
1. Core · Mechanism
Read every scenario without motion
Published note
- Choose public note
The client prepared GET /service/tea for the public catalog. Choosing the key is only read intent; the controller, service, repository and renderer have not received this request.
- Duru / Client
- GET /service/tea ready
- HTTP controller
- No request
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Request arrived
The controller received the public GET request; this arrival has not yet produced a service/domain result.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/tea received
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Use-case call
The controller passes the key to the service; this same-process function call is not drawn as a network packet.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/tea received
- Service / domain
- Query tea
- Repository
- No read
- Template renderer
- Not called
- Read record
The repository reads the harmless fixture record; finding data alone does not establish its availability decision.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/tea received
- Service / domain
- Query tea
- Repository
- Found; published=true
- Template renderer
- Not called
- Rule result
The tea record exists and satisfies published=true. The service prepares available with a title-only view model, title=Tea & Bread. The whole internal repository record and any new write are outside this presentation decision.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/tea received
- Service / domain
- Available; title=Tea & Bread
- Repository
- Found; published=true
- Template renderer
- Not called
- Render text
The fixed h1 HTML text slot transforms title=Tea & Bread into body <h1>Tea & Bread</h1>. The raw title stays unchanged; the ampersand is represented for this context. The body is ready but the client has not received it.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/tea received
- Service / domain
- Available; title=Tea & Bread
- Repository
- Found; published=true
- Template renderer
- <h1>Tea & Bread</h1> ready
- HTTP mapping
The controller maps the domain result to the selected HTTP policy; this read-only action is not a database commit.
- Duru / Client
- Waiting response
- HTTP controller
- 200 HTML ready
- Service / domain
- Available; title=Tea & Bread
- Repository
- Found; published=true
- Template renderer
- <h1>Tea & Bread</h1> ready
- Response arrived
The client now received the 200 response and <h1>Tea & Bread</h1> body. This arrival differs from the earlier service/renderer result. Browser parsing, DOM/pixels and database commit are not shown by this step.
- Duru / Client
- 200 HTML received
- HTTP controller
- 200 HTML ready
- Service / domain
- Available; title=Tea & Bread
- Repository
- Found; published=true
- Template renderer
- <h1>Tea & Bread</h1> ready
Draft note
- Choose public note
The client prepared GET /service/draft for the public catalog. Choosing the key is only read intent; the controller, service, repository and renderer have not received this request.
- Duru / Client
- GET /service/draft ready
- HTTP controller
- No request
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Request arrived
The controller received the public GET request; this arrival has not yet produced a service/domain result.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/draft received
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Use-case call
The controller passes the key to the service; this same-process function call is not drawn as a network packet.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/draft received
- Service / domain
- Query draft
- Repository
- No read
- Template renderer
- Not called
- Read record
The repository reads the harmless fixture record; finding data alone does not establish its availability decision.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/draft received
- Service / domain
- Query draft
- Repository
- Found; published=false
- Template renderer
- Not called
- Rule result
The draft record exists but published=false fails public availability. The service returns unavailable; no title view model or renderer call is produced.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/draft received
- Service / domain
- Unavailable
- Repository
- Found; published=false
- Template renderer
- Not called
- HTTP mapping
The controller maps unavailable to 404 and the Not available body under this public-read contract. Draft and absent receive the same external response; the renderer is not called and no persistence write/commit occurs.
- Duru / Client
- Waiting response
- HTTP controller
- 404 Not available ready
- Service / domain
- Unavailable
- Repository
- Found; published=false
- Template renderer
- Not called
- Response arrived
The client received 404 Not available. This external result alone cannot distinguish an existing unpublished draft from a missing absent key; the internal cause requires repository/domain evidence.
- Duru / Client
- 404 Not available received
- HTTP controller
- 404 Not available ready
- Service / domain
- Unavailable
- Repository
- Found; published=false
- Template renderer
- Not called
Missing note
- Choose public note
The client prepared GET /service/absent for the public catalog. Choosing the key is only read intent; the controller, service, repository and renderer have not received this request.
- Duru / Client
- GET /service/absent ready
- HTTP controller
- No request
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Request arrived
The controller received the public GET request; this arrival has not yet produced a service/domain result.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/absent received
- Service / domain
- Not called
- Repository
- No read
- Template renderer
- Not called
- Use-case call
The controller passes the key to the service; this same-process function call is not drawn as a network packet.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/absent received
- Service / domain
- Query absent
- Repository
- No read
- Template renderer
- Not called
- Read record
The repository reads the harmless fixture record; finding data alone does not establish its availability decision.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/absent received
- Service / domain
- Query absent
- Repository
- No record
- Template renderer
- Not called
- Rule result
The repository found no record for key absent. The service returns unavailable without assigning a published value to a nonexistent record. No title view model or renderer call is produced.
- Duru / Client
- Waiting response
- HTTP controller
- GET /service/absent received
- Service / domain
- Unavailable
- Repository
- No record
- Template renderer
- Not called
- HTTP mapping
The controller maps unavailable to 404 and the Not available body under this public-read contract. Draft and absent receive the same external response; the renderer is not called and no persistence write/commit occurs.
- Duru / Client
- Waiting response
- HTTP controller
- 404 Not available ready
- Service / domain
- Unavailable
- Repository
- No record
- Template renderer
- Not called
- Response arrived
The client received 404 Not available. This external result alone cannot distinguish an existing unpublished draft from a missing absent key; the internal cause requires repository/domain evidence.
- Duru / Client
- 404 Not available received
- HTTP controller
- 404 Not available ready
- Service / domain
- Unavailable
- Repository
- No record
- Template renderer
- Not called
ACTORS · STATE · DATA FLOW
The controller carries results; the domain selects them
A service layer organizes a use-case flow: invokes suitable data access, applies a domain rule, and returns a result the controller can consume. This public-note rule permits presentation when published=true. The controller maps a path to a note key, the repository reads the record, and the domain determines availability. Service does not mean a remote HTTP microservice or separate process here; these are function calls inside the selected process.
Business logic defines what the application considers valid; choosing HTML tags or writing headers is a different responsibility. In a DDD design, the application layer orchestrates while the domain owns business rules. Smaller applications can use a different structure. Ask who makes the published decision across HTML and JSON presentations, rather than relying on class names.
The Atlas repository defines tea as published=true and draft as published=false; absent finds no record. The first becomes a view model; the other two map to the same 404 Not available response. That status does not disclose which record exists. This is a public-catalog availability rule, not an identity or object-authorization policy. Successful data access also differs from permission to present a record.
Templates place data in an interpreter context
A template is a trusted output structure selected by the application; its view model is data. Placing Tea & Bread in the HTML text slot produces <h1>Tea & Bread</h1>. A JSON representation can retain Tea & Bread as its string value. Encoding decisions cannot be copied blindly between contexts: HTML text, attributes, URLs, JavaScript, and CSS have different interpretation rules.
The ampersand transformation for this fixed harmless title does not prove an entire engine secure. Production work must understand engine auto-escaping and permitted output contexts. Keeping template source separate from variable values supports a design in which displayed strings are not reinterpreted as programs. This lesson uses one fixed HTML text slot, with no dynamic template code or executable context.
Express res.render documents combining a template with values. Its view engine cache holds the underlying template rather than one ready-made body for every request. Our small renderer also yields different bodies for Tea & Bread and Coffee in the same fixed structure. It is not an Express/Pug execution. A completed service result or response.end call is not a database transaction commit; this read-only example has no persistence operation.
2. Red · Trust & Impact
Read every scenario without motion
Draft rule
- The public-catalog rule was selected
The rule requires record exists AND published=true. A repository finding a record alone is not availability for this public presentation.
- Catalog input/record
- Key=draft
- Service/render rule
- Service rule not applied
- Presentation boundary
- Renderer not called
- The repository result was read in scope
The draft row has repositoryFound=true. This store result is not broadened into identity acceptance, publishing permission or client-delivery approval.
- Catalog input/record
- Repository found=true
- Service/render rule
- Published state not read
- Presentation boundary
- Renderer not called
- The published gate was preserved
published=false gives serviceOutcome=unavailable. An HTML or JSON adapter exposing the repository record outside this rule would not preserve the same contract.
- Catalog input/record
- Record exists; unpublished
- Service/render rule
- Service unavailable
- Presentation boundary
- Renderer not called
- Renderer dispatch was withheld
rendererInvoked=false remains. A title view model is not treated as prepared for the existing draft; service outcome constrains presentation input.
- Catalog input/record
- Record exists; unpublished
- Service/render rule
- Availability gate denied
- Presentation boundary
- No renderer dispatch
- HTTP mapping was separated from the domain outcome
Status 404 with body Not available is this product’s unavailable mapping. It does not prove repository absence; the absent row receives the same response.
- Catalog input/record
- Known: draft exists
- Service/render rule
- Known: unavailable
- Presentation boundary
- Declared 404 Not available
- Presentation authority stayed with its owner
This public rule is not identity policy. Preserving the draft response does not validate all private-access controls; this example has no writes or credentials.
- Catalog input/record
- Public availability rule
- Service/render rule
- No publish bypass assumed
- Presentation boundary
- No broader authorization claim
HTML text
- The fixed template slot was selected
Normal title Tea & Bread is harmless data. The fixed h1 text slot and data input remain separate responsibilities.
- Catalog input/record
- Title: Tea & Bread
- Service/render rule
- HTML text slot
- Presentation boundary
- Output not prepared
- The view model was taken after the rule
The available normal row carries a title-only view model. The repository’s entire internal record is not automatically supplied as editable template context.
- Catalog input/record
- View model: title only
- Service/render rule
- Fixed HTML text slot
- Presentation boundary
- Output not prepared
- Text-context encoding was applied
The HTML body is <h1>Tea & Bread</h1>. Ampersand data is represented in HTML text context; the transformation creates no new template source.
- Catalog input/record
- Raw title unchanged
- Service/render rule
- Text encoder selected
- Presentation boundary
- Encoded h1 body
- The encoder was not carried into another context
This HTML text encoder is not a universal control for URL or script contexts. A new context requires its own appropriate rule and sink policy.
- Catalog input/record
- Harmless title data
- Service/render rule
- HTML text scope only
- Presentation boundary
- No universal encoder claim
- A serialized body was not treated as receipt
A prepared HTML body is not the moment of client receipt or browser parsing. Completion retains serviceResultIsClientArrival=false.
- Catalog input/record
- View model known
- Service/render rule
- HTML body prepared
- Presentation boundary
- Client arrival not proven
- The read-only output boundary was preserved
persistenceOperation=null and commitProven=false. Correct text encoding proves neither record mutation nor the safety of every template-engine option.
- Catalog input/record
- Known: selected text transformation
- Service/render rule
- Fixed data/source boundary
- Presentation boundary
- No commit or universal rendering claim
ASSUMPTION · BOUNDARY · CONTROL
Ownership of one rule across different entry points
Start by identifying which service/domain result each controller consumes. If an HTML route gets published from the service while a JSON route directly serializes repository records, one availability contract is applied differently. The possible impact is a broken intended presentation boundary; a method or framework name cannot explain it. The control is a shared rule owner and evidence that both presentations depend on its result.
A 404 for draft does not prove an empty repository: the service can mark an existing record unavailable. absent deliberately has the same response. When outside evidence cannot distinguish those causes, report only the response contract. Controller, service-result, and repository observations together clarify the boundary. These are permitted benign catalog states, with no attack or authorization bypass.
Interpreting output and completion accurately
The encoded Tea & Bread HTML body demonstrates data transformation for the selected text slot. Reusing that transformation as a URL or script-context security control changes its meaning. The failure condition is a mismatch between context and encoder; possible effects include incorrect rendering or unexpected interpretation. Keep contexts fixed, use appropriate engine/library controls, and review the reasons for raw-output choices.
A ready view model, serialized body, and response received by a client are separate stages. None alone establishes persistence. Reviewing a write use case would require commit results, rollback boundaries, and response policy; there are no writes here. Different renderer outputs for two titles prove per-input transformation only, not cache isolation, browser parsing, or every template-engine option.
3. Blue · Secure development & defense
Read every scenario without motion
404 reasons
- Draft and absent observations were selected
Both rows give status=404 and body Not available. Review separates equal public responses from internal availability causes.
- Service/renderer observation
- Draft + absent rows
- Cause/input review
- Cause fields not read
- Evidence conclusion
- External cause unknown
- The draft-cause field was read
Draft has repositoryFound=true and published=false. Its service-unavailable outcome follows an existing record failing the public rule.
- Service/renderer observation
- Draft exists; unpublished
- Cause/input review
- Draft: unavailable
- Evidence conclusion
- Response: 404 Not available
- The absent-cause field was read
Absent has repositoryFound=false. This is a second internal cause for the same serviceOutcome; the public body need not distinguish them externally.
- Service/renderer observation
- Absent record not found
- Cause/input review
- Absent: unavailable
- Evidence conclusion
- Response: 404 Not available
- Renderer withholding was compared
Both rows have rendererInvoked=false. Gate outcome and renderer dispatch are compared independently of status.
- Service/renderer observation
- Two unavailable reasons
- Cause/input review
- No renderer in either row
- Evidence conclusion
- Public responses equal
- Cause was not assigned from external status alone
A consumer observing only 404 cannot distinguish draft from absent under this contract. The internal ledger supplies the distinction; without it, cause remains UNKNOWN.
- Service/renderer observation
- Public 404 observation only
- Cause/input review
- Internal fields required
- Evidence conclusion
- UNKNOWN from status alone
- The availability conclusion stayed bounded
The supplied ledger explains two benign causes. A 404 establishes neither attack attribution, private authorization nor storage failure.
- Service/renderer observation
- Known: declared unavailable reasons
- Cause/input review
- Known: renderer withheld
- Evidence conclusion
- Other causes require separate evidence
Render input
- Two renderer outcomes were selected
Normal title Tea & Bread and secondRender title Coffee are different data. sameTemplate=true shows the same template receiving different inputs.
- Service/renderer observation
- Two title inputs
- Cause/input review
- Output not read
- Evidence conclusion
- No browser evidence
- Normal HTML output was read
The normal body is <h1>Tea & Bread</h1>. It describes fixed text-slot encoding, not an actual browser DOM.
- Service/renderer observation
- Tea & Bread input
- Cause/input review
- Encoded Tea & Bread output
- Evidence conclusion
- No browser evidence
- The second output was compared
Coffee input produces <h1>Coffee</h1> with sameOutput=false. A template cache does not mean that all rendered bodies are identical.
- Service/renderer observation
- Coffee input
- Cause/input review
- Same template; different output
- Evidence conclusion
- No browser evidence
- Transformation evidence stayed in context
The bodies show only per-input transformation. Cache isolation, engine auto-escaping options and script/URL contexts lie outside their scope.
- Service/renderer observation
- Two fixed public inputs
- Cause/input review
- Selected HTML text context
- Evidence conclusion
- No universal engine claim
- Client-knowledge evidence was separated
Prepared strings do not establish browser parsing, DOM layout or rendered pixels. Client-receipt and render observations require their own sources.
- Service/renderer observation
- Prepared body strings
- Cause/input review
- Known: transformation outputs
- Evidence conclusion
- UNKNOWN: client rendering
- The renderer conclusion was not broadened
sameTemplate=true and sameOutput=false are consistent here. No conclusion is made about database commits or actual framework-cache behavior.
- Service/renderer observation
- Known: same template
- Cause/input review
- Known: different title outputs
- Evidence conclusion
- UNKNOWN: broader engine/runtime behavior
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Record boundaries separately in the decision ledger
The ledger records repositoryFound=true, published=true, serviceOutcome=available, and rendererInvoked=true for tea. draft exists but becomes unavailable with no renderer invocation; absent has no record. These are selected teaching observations, not a product log schema. Reading outcome-to-status mapping independently separates HTTP policy from a business rule.
Control equivalence requires more than matching status codes. Check that unavailable never reaches rendering, that available supplies only the title view model, and that JSON/HTML adapters consume the same result. A read-only repository call demonstrates record access; a prepared response proves neither client arrival nor database commit. The artifact explicitly separates and denies those latter claims.
Limits of the selector and context check
XPath count(/audit/decision[@outcome="unavailable" and @rendered="true"]) counts authored observations that reach rendering despite an unavailable decision. Normal data gives 0; a deliberately inconsistent positive fixture gives 1. A normal draft 404 is benign and expected. An absent event can also produce 0, meaning insufficient evidence rather than proven rule enforcement. The selector evaluates this observation contract only.
Review encoding with rawTitle and htmlBody together: Tea & Bread becomes Tea & Bread in HTML text. No other context is used, so the evidence stays narrow. Express view caching is a documented behavior; the small renderer demonstrates different inputs producing different bodies through one function. Actual engine auto-escaping, middleware, browser DOM, transactions, and production caching require their own evaluation.
Keep the business decision independent of adapters
Finding a repository record is not permission to publish it. The service explicitly evaluates published and unavailable; JSON and HTML adapters must not bypass that shared decision. The renderer receives only the required title view model rather than automatically exposing every repository field. Use the appropriate encoder or verified engine auto-escaping for HTML text; JSON serialization is a different control.
Verify that both adapters consume available for tea and that draft never invokes rendering. Tea & Bread becomes Tea & Bread in HTML text without changing the original title. Moving the value to an attribute or another context requires reconsidering the encoder. If the service decision or renderer invocation is not observed, a 404 or plausible body alone cannot establish enforcement.
NOW APPLY IT TO THE CASE
Which result is carried by business rules and text transformation?
The public-catalog rule treats drafts and absent records as unavailable. When an available title is transformed into a fixed text slot, rendered strings and client receipt remain separate.
Reconstruct decision order for input, server processing and response in the case. Parsed or valid data is not a completed operation; identify each owner and its condition.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Public-note decision and render ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"rule": {
"name": "public catalog availability",
"predicate": "record exists AND published=true",
"scope": "public read-only synthetic notes, no identity policy"
},
"normal": {
"key": "tea",
"repositoryFound": true,
"published": true,
"serviceOutcome": "available",
"rendererInvoked": true,
"viewModel": {
"title": "Tea & Bread"
},
"htmlBody": "<h1>Tea & Bread</h1>",
"status": 200
},
"draft": {
"key": "draft",
"repositoryFound": true,
"published": false,
"serviceOutcome": "unavailable",
"rendererInvoked": false,
"status": 404,
"body": "Not available"
},
"absent": {
"key": "absent",
"repositoryFound": false,
"serviceOutcome": "unavailable",
"rendererInvoked": false,
"status": 404,
"body": "Not available"
},
"secondRender": {
"title": "Coffee",
"htmlBody": "<h1>Coffee</h1>",
"sameTemplate": true,
"sameOutput": false
},
"completion": {
"persistenceOperation": null,
"commitProven": false,
"serviceResultIsClientArrival": false
},
"selector": {
"language": "XPath",
"xml": "<audit><decision outcome=\"unavailable\" rendered=\"false\"/></audit>",
"xpath": "count(/audit/decision[@outcome=\"unavailable\" and @rendered=\"true\"])",
"expected": 0,
"positiveFixtureXml": "<audit><decision outcome=\"unavailable\" rendered=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- serviceOutcome
- Domain decision between repository result and HTTP status.
- htmlBody
- Encoded output of one fixed HTML text context.
- completion
- Boundary that does not equate result, arrival, and commit.
Fixed benign notes, in-memory repository, and tiny renderer; not an Express/Pug, transaction, or browser DOM capture.
Two reality checks
A service layer is always a remote HTTP service.
Here controller, service, repository, and renderer share a process; distinct responsibilities do not require network hops.
A template cache gives every request the same ready-made HTML.
Express view caching retains the underlying template; output is re-rendered with data. Output caching is a separate decision.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The repository found a draft row but published=false. Does the public service call the renderer?
- No; unavailable/404 and rendererInvoked=false.
- Yes; a public template always runs when a data row is found.
Read the explained answer
No; unavailable/404 and rendererInvoked=false. — The rule requires record exists AND published; found alone is not public availability.
Why does Tea & Bread become Tea & Bread in HTML source?
Selected lesson artifact fields; teaching example
- normal.viewModel
- {"title":"Tea & Bread"}
- normal.htmlBody
- <h1>Tea & Bread</h1>
- The template transforms the title into another business record.
- Text-context encoding preserves the literal ampersand through representation.
Read the explained answer
Text-context encoding preserves the literal ampersand through representation. — ViewModel is text data; encoded HTML source uses different bytes while preserving the intended visible title.
The same template produces different output for Coffee. Explain service/repository/template and client-arrival roles.
Read the explained answer
The repository retrieves a record; the service establishes the published predicate and viewModel. A template generates context-appropriate HTML from the viewModel; different input yields different output. A service result is not client arrival; this read-only example has persistenceOperation null and commitProven=false.
□ I distinguished data-access/business-rule/presentation roles.
□ I did not count a template result as delivery/commit.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- Microsoft Learn — Designing a DDD-oriented microserviceLayers in DDD microservices · The domain model layer · The application layer · The infrastructure layer
- Express — Using template enginesUsing template engines with Express · res.render and __express convention · View engine cache paragraph
- OWASP — Cross Site Scripting Prevention Cheat SheetOutput Encoding · Output Encoding for HTML Contexts · Output Encoding for HTML Attribute Contexts
- Node.js v24.13.0 — HTTP APIhttp.createServer([options][, requestListener]) · response.end([data[, encoding]][, callback]) · response.writableEnded · response.writableFinished
- RFC 9110 — HTTP Semantics3.1 Resources · 3.2 Representations · 8.3 Content-Type · 8.6 Content-Length · 9.2.1 Safe Methods · 9.2.2 Idempotent Methods · 9.3.1 GET · 9.3.2 HEAD · 12.5.1 Accept · 15.5.5 404 Not Found · 15.5.6 405 Method Not Allowed · 15.5.7 406 Not Acceptable