Module (Bounded Context)
A bounded context: a related set of actions, events, actors, projections, queries, reactions, and orchestrator primitives that belong together.
Shape
ts
import { defineModule } from "@nwire/forge";
export const submissionsModule = defineModule("submissions", {
description: "Submission lifecycle: receive → grade → review.",
owners: ["curriculum-eng"],
journey: [
{ id: "J3-submit", label: "Submit an answer" },
{ id: "J4-flag", label: "Auto-grader flags low-confidence work" },
{ id: "J5-grade", label: "System grades a submission" },
{ id: "J6-review", label: "Reviewer dispositions a flagged submission" },
],
// Domain primitives
actions: [submitAnswer, gradeSubmission, flagForReview, reviewSubmission],
events: [
AnswerSubmittedEvent,
SubmissionAutoGradedEvent,
SubmissionFlaggedForReviewEvent,
SubmissionManuallyGradedEvent,
],
actors: [Submission],
projections: [SubmissionsByStudent],
queries: [submissionsByStudent],
reactions: [autoGradeReaction],
handlers: [/* if any are registered separately from their action */],
// Orchestrator primitives
externalCalls: [notifyStudent, chargeStripe],
inboundWebhooks: [stripeWebhook],
outboxes: [submissionsOutbox],
inboxes: [submissionsInbox],
crons: [dailyReviewSummary],
// HTTP routes
routes: defineRoute({
"POST /submissions": submitAnswer,
"GET /submissions": submissionsByStudent,
"POST /submissions/:id/flag-for-review": flagForReview,
}),
// Cross-module declared dependencies
needs: {
events: [], // public events from OTHER modules I subscribe to
externalEvents: [], // events from OTHER services (via bus)
actions: [], // actions from OTHER modules I dispatch
},
});needs — explicit dep graph
createApp validates at boot that every needs.events / needs.actions is provided by some other module. Catches typos + missing modules before the first dispatch.
ts
defineModule("mastery", {
needs: {
events: [SubmissionAutoGradedEvent, SubmissionManuallyGradedEvent],
},
reactions: [updateOnGradedReaction, updateOnReviewedReaction],
});If SubmissionAutoGradedEvent is visibility: 'internal', createApp throws — internal events can't be subscribed by other modules.
What modules SHOULD look like
- ✅ One bounded context per module
- ✅ Domain shapes live in
context/ - ✅ Actions + handlers live in
handlers/ - ✅ Reactions live in
reactions/ - ✅ Projections + queries live in
projections/ - ✅ External boundaries live in
external/ - ✅ HTTP routes in
routes/
(See Architecture principles for the canonical folder shape.)
Reusable modules vs consumer modules
- Consumer modules (in your app's repo):
submissions,enrollments,mastery— your business - Reusable modules (in
nwire/modules/):auth,billing,notifications— domain-agnostic, shipped by the framework
Studio-aware metadata
| Field | What Studio does |
|---|---|
description | Module tooltip + detail panel |
owners | Team tag for who maintains the module |
journey: [{ id, label }] | Drives the EventStorm Process Flow journey picker |
See also
- defineModule
- App — apps compose modules
- Orchestrator — external boundary primitives