The starting point.
The domain combined archival sessions, session types, organisation codes, input and output storage, source packages, resulting packages and workflow lifecycle. Operators needed one practical way to see how those records moved together.
The feature had to live inside a larger single-spa platform that already supplied its own header, footer, navigation and reusable microfrontends. Reusing those components was a primary requirement, not a later visual tidy-up.
A standalone React application would have duplicated the platform shell and created a second place to maintain navigation and chrome. The useful boundary was a focused archival experience that the existing platform could compose.
What the design had to solve.
The useful boundary sat between process state, operator action and the host platform:
- A session, its source packages and resulting output packages needed to stay understandable across a nine-step lifecycle.
- Operator actions had to map cleanly to valid process transitions.
- The new experience had to reuse the platform header, footer, navigation and existing component conventions instead of shipping a parallel shell.
- The React single-spa microfrontend needed a narrow mount point and integration contract so archival rules did not spread into the host.
- Backend rules, Camunda state and frontend actions could not drift into separate interpretations of the same workflow.
The diagnosis.
Camunda could coordinate the process, but orchestration alone was not the operator experience. The missing layer was a coherent application boundary around the workflow.
The host platform already solved composition and chrome. Rebuilding those parts would have increased code without improving the archival workflow, so I limited the microfrontend to the domain surface the platform did not have.
I separated ownership deliberately: the host keeps navigation and shared components; the Spring Boot service owns application rules, S3-compatible storage references and Camunda integration; the React single-spa microfrontend presents current state and valid actions.
The resulting application slice.
The existing single-spa shell mounts the React microfrontend inside its shared header, footer and navigation. The microfrontend gives operators the archival surface only. A focused Spring service translates application actions into workflow operations and storage references, while Camunda coordinates the process from session creation through output tracking.
Why these boundaries fit the host platform.
This was not a greenfield stack selection. The shell, workflow engine and storage conventions belonged to an existing platform; the design task was to add one coherent capability without duplicating or bypassing those boundaries.
A focused Spring Boot service boundary
- Why
- The service gave application rules, valid actions, Camunda integration and storage references one backend owner. React could consume an application model instead of learning process-engine mechanics.
- What I ruled out
- Calling Camunda directly from the browser or distributing archival rules through shared services would expose orchestration details and create more than one interpretation of valid workflow behaviour.
- What it cost
- The additional API and mapping layer must evolve with the process, and errors at the service-to-engine boundary need explicit handling.
Camunda for the nine-step process
- Why
- Camunda already coordinated the workflow, so keeping process transitions there preserved one source for progress while the service translated that state for operators.
- What I ruled out
- A custom state machine, especially one represented mainly as UI state, would duplicate process logic and make browser state compete with the workflow engine for authority.
- What it cost
- Process definitions and application models must stay aligned, and operational diagnosis crosses both the service and the engine.
React in the existing single-spa composition
- Why
- A bounded microfrontend could reuse the platform header, footer, navigation and component conventions while owning only the archival route and interaction model.
- What I ruled out
- Rewriting the shell or shipping a standalone React application would expand the project beyond the missing capability and create duplicate navigation and chrome to maintain.
- What it cost
- The microfrontend depends on a stable mount and integration contract, and shared-platform changes require coordination rather than complete local control.
S3-compatible storage for input and output packages
- Why
- Package content stayed at an object-storage boundary while the service and workflow referred to it, keeping archival records and binary payload ownership distinct.
- What I ruled out
- Database blobs would bind package content to the relational data lifecycle, while local files would bind it to a particular service instance and deployment path.
- What it cost
- Buckets, endpoints, credentials and missing-object cases become explicit operational concerns that the service must represent honestly.
Rules for an operable workflow.
The architecture provides one source of process truth; these rules turn it into an experience an operator can read, trust and recover.
Show state before offering action
The current session, its source and output packages, and the process stage stay visible before an operator chooses the next step. An action without that context is not presented as progress.
The trade-offThe interface must explain unavailable, pending and failed states instead of optimising only for the happy path.
Derive valid actions from backend state
The interface presents transitions supported by the application model, while the service rejects invalid ones. Local browser state never becomes a second authority over what may happen next.
The trade-offState exchange and refresh behaviour must be explicit, but a stale screen cannot invent a valid workflow step.
Preserve session and package continuity
The session, source packages and resulting packages remain connected across the nine-step route. Verification follows that same journey from creation and attachment through processing and output tracking.
The trade-offThe model and interface carry more context than a task-only screen, but operators can follow how the work and its output belong together.
Make failure and retry visible
An incomplete, pending or failed step remains visible. If the next valid action allows a retry or continuation, it appears only after the backend confirms that state.
The trade-offThis increases the number of states and error paths the interface must explain, but distinguishes waiting, failure and recovery instead of leaving operators to guess.
Delivery and verification.
Verification follows the same nine-step route an operator uses: create a session, attach source packages, start the workflow, observe valid transitions and track output packages when processing completes.
I also checked the composition boundary: the new route had to sit inside the existing header, footer and navigation without introducing a second shell or pushing archival state into shared platform components.
The important failure cases sit at the other boundaries. The service must reject invalid transitions, the UI must show incomplete or pending state honestly, and the process integration must remain the source of workflow truth.
The qualitative result.
The resulting slice covers session handling, package processing and workflow control from the operator interface through to the Camunda process.
Operators get one place to understand the work and move it forward. The wider platform keeps the header, footer, navigation and visual conventions they already know.
The platform gains a contained capability rather than a parallel application: no duplicated shell, no second navigation model and no archival rules distributed across unrelated microfrontends.
The organisation, product name, internal session types, storage endpoints, bucket names, credentials and delivery dates are omitted. The domain lifecycle comes from the project record.