Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions adr/002-backend-data-initialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# ADR: Move most initial data loading to app-backend

- **Status:** Proposed
- **Date:** 2025-09-23
- **Owner:** Team Studio
- **Related:** Altinn/altinn-studio#16309 (Epic) ([GitHub][1])

## Context

Today `app-frontend` bootstraps by mounting a stack of query providers (TanStack Query), each fetching pieces of application/instance/party/config data on load. This creates:

- Many requests and duplicated invalidation logic
- Complex provider/effect nesting and brittle boot order
- Longer TTFB/TTI and heavier cognitive load for app developers

Epic #16309 proposes shifting _initial_ data orchestration to `app-backend` and delivering a consolidated payload to the client at bootstrap, reducing round trips and complexity. ([GitHub][1])

## Decision

We will:

1. **Generate the bootstrap HTML in `HomeController` (app-backend).**
Backend takes ownership of serving the app shell (replaces handcrafted Index.cshtml).
2. **Adopt path-based routing (no `HashRouter`).**
Backend resolves/serves deep links, delegating route rendering to `app-frontend`. Back-compat preserved for legacy hash URLs.
3. **Embed immutable “AppData” in the HTML.**
Static, versionable, cross-variant data is serialized into a global (e.g., `window.AltinnAppData = {...}`) in the bootstrap page.
4. **Provide an “InitialInstanceData” bundle when opening an existing instance.**
When backend detects an instance context, it embeds (or exposes via single endpoint) the static-at-boot instance data adjacent to `AppData`, so the client does not immediately refetch/invalidate. Further interactions use targeted APIs. ([GitHub][1])
5. **Remove RuleHandler/RuleConfiguration in this major.**
Do not migrate them to the new boot flow (handled by separate task).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also make a point of measuring the baseline, so that we can also measure the impact of these changes? 100% agreed on the principles of this, but would be even better if we had some data to point to. If we feel like auomated testing is out of scope, we could just include some manual measurements as text here based on a couple of waterfalls that are problematic/that we expect to improve. Examples

  • Opening instance deeplink
  • Render PDF
  • ^ with and without subforms?

Even if it were just screenshots embedded into the markdown here it would help to motivate decisions and clarify which parts of the waterfalls are most important (or even not covered by the current proposal).
In the future we should have tests as code as well that can be ran to verify changes in performance over time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you mention TTFB/TTI, could include those perhaps

In scope: catalog and migrate all initial queries across stateless, stateless-anon, and stateful apps. Out of scope: data that must be updated _after_ boot.

```mermaid
graph LR
A[User's Browser]:::actor

subgraph S1["Before — frontend orchestrates boot"]
A --> FFE[app-frontend]
FFE -->|fetch on mount| Q1[GET /texts]
FFE -->|fetch on mount| Q2[GET /app-metadata]
FFE -->|fetch on mount| Q3[GET /profile/party]
FFE -->|fetch on mount| Q4[GET /instances?active=true]
FFE -->|fetch on mount| Q5[GET /orgs]
FFE -->|fetch on mount| Q6[GET /ui-config]
FFE -.-> FX[Complex provider/effect graph]
end

subgraph S2["After — backend assembles initial payload"]
A --> BE[app-backend HomeController]
BE -->|orchestrates| P1[(Platform APIs)]
P1 --> BE
BE -->|Bootstrap HTML + AppData + InitialInstanceData| A
A --> FFE2[app-frontend hydrate & render]
FFE2 -->|reads global variable| QC[global state variables]
FFE2 -->|targeted fetch on demand| P2[(Platform APIs)]
end

classDef actor fill:#eef,stroke:#77f,color:#000
```

## Rationale

- **Performance:** Fewer requests and no waterfall; better TTFB/TTI by shipping what the app needs up front.
- **Simplicity:** One place orchestrates initial data; frontend boot becomes deterministic.
- **Reliability:** Backend can decide what to load based on route/instance/party and enforce pagination/limits.

## Considered Options

1. **Status quo (frontend-orchestrated queries).**
Rejected: retains complexity and network overhead.
2. **Bulk endpoints.**
Rejected: increases backend complexity without clear benefit vs app-backend controller.

## Technical Notes (non-normative)

- **Bootstrap contract**

```html
<script
id="__appdata"
type="application/json"
>
{ ... AppData ... }
</script>
<script
id="__initialInstanceData"
type="application/json"
>
{ ... } <!-- present only when opening an instance -->
</script>
<script>
window.AltinnAppData = JSON.parse(document.getElementById('__appdata').textContent);
</script>
<script>
window.AltinnInitialInstanceData = JSON.parse(
document.getElementById('__initialInstanceData')?.textContent || 'null',
);
</script>
```

- **Routing:**
- Backend maps `/` `/instances/:id/*` `/tasks/:taskId` etc. to a single `HomeController` action that embeds the correct initial payload and serves static assets.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it actually possible to make this work with our current API routes? E.g. we have {org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceGuid:guid} as a route in InstancesController. Do we get a collision if we remove the hash? Or was the frontend route instance (singular)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! The frontend route is singular; instance.

- Legacy `/#/...` routes: frontend redirects to updated routes.

- **Pagination upgrades:**
- Add pagination on _active instances_ and _parties_ APIs as specified in tasks. Cursor-based preferred.

## Consequences

**Positive**

- Fewer network round trips at boot; smaller provider graph in UI.
- Clearer ownership boundaries: backend decides _what_; frontend renders _how_.
- Easier to test: snapshot input JSON → deterministic initial UI.

**Negative / Trade-offs**

- Slightly more logic in backend (must maintain schema for `AppData`/`InitialInstanceData`).
- Coordinated deploys required when the bootstrap contract changes.
- Initial HTML grows; ensure gzip/brotli and avoid embedding large option lists—keep those via APIs.

## Security & Compliance

- Ensure embedded data contains only what the current principal may see.

## Rollout Plan
Comment on lines +124 to +128
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Harden security: escaping, CSP, and caching directives for embedded JSON.

Embedding instance/app data in HTML has XSS and caching risks. Document the requirements here.

 ## Security & Compliance
@@
-- Ensure embedded data contains only what the current principal may see.
+- Ensure embedded data contains only what the current principal may see.
+- Escape sequences that could break out of the JSON/script context (e.g., `</script`), or use a safe JSON serializer that guarantees this.
+- Set a strict CSP with nonce/hashes; forbid `unsafe-inline`. Example: `script-src 'self' 'nonce-…'`.
+- Serve bootstrap responses with `Cache-Control: private, no-store` (at minimum for instance-specific payloads) and `Vary: Cookie, Authorization` to avoid proxy/CDN leaks.
+- Set `X-Content-Type-Options: nosniff`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Security & Compliance
- Ensure embedded data contains only what the current principal may see.
## Rollout Plan
## Security & Compliance
- Ensure embedded data contains only what the current principal may see.
- Escape sequences that could break out of the JSON/script context (e.g., `</script`), or use a safe JSON serializer that guarantees this.
- Set a strict CSP with nonce/hashes; forbid `unsafe-inline`. Example: `script-src 'self' 'nonce-…'`.
- Serve bootstrap responses with `Cache-Control: private, no-store` (at minimum for instance-specific payloads) and `Vary: Cookie, Authorization` to avoid proxy/CDN leaks.
- Set `X-Content-Type-Options: nosniff`.
## Rollout Plan
🤖 Prompt for AI Agents
adr/002-backend-data-initialization.md around lines 124-128: add concrete
security and caching requirements for any embedded JSON—require server-side
filtering to include only fields allowed for the current principal, serialize
with JSON.stringify and HTML-escape characters that can break HTML (<, >, &, /)
or prefix the payload (e.g., )]}',\n) to prevent JSON hijacking; mandate CSP
rules (script-src with nonces/hashes and default-src restrictions) and
X-Content-Type-Options: nosniff; specify caching policy: user-specific pages
must use Cache-Control: private or no-store and set appropriate Vary headers,
whereas truly public data may be cacheable with long max-age and immutable; add
automated tests and documentation notes for developers on safe embedding,
sanitization steps, and example headers.


1**Route migration:** enable path-based routing; ship redirects for legacy hash URLs; validate deep links.
2**Delete old providers/effects** and RuleHandler/RuleConfiguration.
3**Docs & templates:** update app templates and docs; provide migration guide for app owners.

## Impacted Areas

- `app-backend`: `HomeController`, payload assemblers, auth.
- `app-frontend`: routing, bootstrapping, removal of nested providers.
- APIs: instances list, parties list (add pagination).
- `app-template`: must be updated to support new loading.

## Open Questions

- Which option sources (datalists) are always API-fetched vs allowed to be embedded for tiny lists? (Epic notes say datalist+options stay API. Confirm.)
- Error UX if embedded payload fails to parse (serve minimal shell with error boundary?).
- What consequences does removing index.cshtml have and how should we support existing modifications that apps have implemented?

## Acceptance Criteria

- Boot of stateless and stateful apps requires ≤ 2 requests (HTML + assets) before first interactive paint (excluding datalist/options APIs).
- No client refetch for data already embedded at boot.
- Path URLs work for all previously supported deep links; legacy hashes redirect.
- Parties/instances APIs expose tested pagination; UI consumes it.
- Update RuleHandler/RuleConfiguration to mention that this functionality will be removed in future versions.

Comment on lines +153 to +154
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Resolve inconsistency: removal now vs. future removal.

Decision says remove RuleHandler/RuleConfiguration “in this major”, but acceptance criteria says “mention will be removed in future versions”. Align the plan.

-- Update RuleHandler/RuleConfiguration to mention that this functionality will be removed in future versions.
+- Remove RuleHandler/RuleConfiguration in this major and provide migration notes and deprecation notice in release docs.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Update RuleHandler/RuleConfiguration to mention that this functionality will be removed in future versions.
- Remove RuleHandler/RuleConfiguration in this major and provide migration notes and deprecation notice in release docs.
🤖 Prompt for AI Agents
In adr/002-backend-data-initialization.md around lines 153-154, the ADR
currently contradicts itself by stating RuleHandler/RuleConfiguration will be
removed "in this major" while the acceptance criteria says to "mention will be
removed in future versions"; update the document to make both statements
consistent: choose the intended timeline (remove in this major OR deprecate and
remove in a future version), then edit the decision and acceptance criteria so
they match that choice and reflect the exact migration/deprecation steps and
user-facing notices required.

**Sources:** Epic details and tasks from Altinn/altinn-studio issue #16309. ([GitHub][1])

[1]: https://github.com/Altinn/altinn-studio/issues/16309 'Move most initial data loading to app-backend · Issue #16309 · Altinn/altinn-studio · GitHub'