Business rules belong in services or domain-focused collaborators, not duplicated across controllers and components.
Course navigationPhase 14⌄
Service & Business Logic Architecture
Move orchestration and business invariants into cohesive services/policies so transport and persistence details do not become the domain model.
Phase outcome and project increment
Learning outcome
Move orchestration and business invariants into cohesive services/policies so transport and persistence details do not become the domain model.
Concrete deliverable
Refactored task service with explicit policies/invariants and thin controllers.
Principles to keep while coding
Keep orchestration separate from low-level persistence details so behavior can be tested clearly.
Make rule boundaries explicit before adding abstractions such as repositories.
Prefer small cohesive methods that communicate intent over giant service methods that mix validation, authorization, and I/O.
Guided phase lab
Do these in order. Each step should end with evidence before the next one starts.
- 01
Choose one endpoint with mixed concerns and list every responsibility it currently performs.
- 02
Move HTTP-only concerns back to controller/boundary and persistence mechanics behind a focused collaborator.
- 03
Express one business rule with a name that describes the rule, not the framework.
- 04
Add a use-case method whose inputs/outputs make orchestration visible.
- 05
Test the rule without booting the whole web server where practical.
- 06
Review the resulting dependency direction and remove abstractions that add no clarity.
Failures you should know how to diagnose
God services with hundreds of unrelated lines
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Business rules duplicated in controllers and frontend
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Repository abstraction added before there is a boundary to protect
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Methods named only after CRUD verbs when workflow intent matters
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Lessons
The source chapters come first. Every phase now ends with a guided build, a debugging workshop, and an independent challenge so reading turns into transferable skill.
Controller vs Service Responsibilities
Controller vs Service Responsibilities Keep HTTP translation thin while providers execute application use cases Phase 14 - Service & Business Logic Architecture Volume 04 - Professional Back
Business Invariants and Rule Ownership
Business Invariants & Rule Ownership Separate transport validation, business rules, authorization, and database constraints Phase 14 - Service & Business Logic Architecture Volume 04 - Profe
Task Status Workflow and Derived State
Task Status Workflow & Derived State Explicit transitions, completedAt consistency, and operations with business meaning Phase 14 - Service & Business Logic Architecture Volume 04 - Professi
Cross Model Rules and Workspace Membership
Cross-Model Rules & Workspace Membership Use relational context to enforce assignee membership invariants Phase 14 - Service & Business Logic Architecture Volume 04 - Professional Backend Ar
Business Validation and Time Based Policies
Business Validation & Time-Based Policies Why semantically valid state needs more than Zod request validation Phase 14 - Service & Business Logic Architecture Volume 04 - Professional Backen
Prisma Transactions and Atomic Workflows
Prisma Transactions & Atomic Workflows Choose nested writes, transaction arrays, or interactive transactions only when a use case needs them Phase 14 - Service & Business Logic Architecture
Service Orchestration and Extraction Thresholds
Service Orchestration & Extraction Thresholds Keep logic readable and know when complexity has earned a new abstraction Phase 14 - Service & Business Logic Architecture Volume 04 - Professio
Anti Patterns and Architecture Theater
Anti-Patterns & Architecture Theater Avoid controller business logic, blind Prisma patches, transaction-everywhere, and premature layers Phase 14 - Service & Business Logic Architecture Volu
Enterprise Transfer HRIS Payroll Procurement Inventory
Enterprise Transfer Map the service-layer mental model to HRIS, payroll, procurement, inventory, and recruitment Phase 14 - Service & Business Logic Architecture Volume 04 - Professional Bac
Hands On Lab and Phase Exit
Hands-On Lab & Phase Exit Workflow rules, membership invariant, transaction reasoning, exercises, and oral exam Phase 14 - Service & Business Logic Architecture Volume 04 - Professional Back
Guided Build Lab
Purpose Move orchestration and business invariants into cohesive services/policies so transport and persistence details do not become the domain model.
Debugging Workshop
Goal Learn to diagnose this phase's failures systematically instead of reinstalling packages, changing random configuration, or copying a fix without understanding it.
Phase Synthesis & Independent Challenge
Mission Rebuild the capability of Phase 14 without following the exact code line-by-line. The goal is transfer: can you use the ideas when the variable names, UI, or route shape changes?
Mastery Capstone Lab
Integrate Phase 14 without following a transcript.
Exercises that increase independence
Choose one endpoint with mixed concerns and list every responsibility it currently performs.
Express one business rule with a name that describes the rule, not the framework.
reproduce and diagnose — God services with hundreds of unrelated lines.
deliver Refactored task service with explicit policies/invariants and thin controllers.
Can place a rule in the correct layer and Can keep controllers thin.
Do not leave the phase only because you reached the bottom
Phase exit criteria
Scenario and concept quiz
These questions test engineering choices rather than whether you remember a lesson title.
Reference files
Attempt the work first. Use snapshots to compare a checkpoint, not as a substitute for understanding.
reference/TaskFlow_Business_Logic_Snapshot/README.txt
TaskFlow Phase 14 reference snapshot This snapshot contains only the files changed or emphasized by Phase 14. It assumes the completed Phase 13 project already exists. Core rules demonstrated: - Controllers remain thin and delegate use cases to TasksService. - TasksService owns Task business invariants. - Task status changes follow an explicit transition policy. - completedAt is synchronized with DONE/reopen transitions. - A Task assignee must be a member of the Task Project's Workspace. - New due dates cannot be in the past (course policy for this application). - A transaction is used for the explicit assignment workflow because the operation reads membership context and then writes the assignment as one use case. - We do not introduce Repository Pattern yet; PrismaService remains the persistence boundary for this phase.
reference/TaskFlow_Business_Logic_Snapshot/apps/api/src/common/errors/application.exceptions.ts
import {
BadRequestException,
ConflictException,
NotFoundException,
} from "@nestjs/common";
export class TaskNotFoundException extends NotFoundException {
constructor(taskId: string) {
super({
code: "TASK_NOT_FOUND",
message: `Task ${taskId} was not found.`,
});
}
}
export class ProjectNotFoundException extends NotFoundException {
constructor(projectId: string) {
super({
code: "PROJECT_NOT_FOUND",
message: `Project ${projectId} was not found.`,reference/TaskFlow_Business_Logic_Snapshot/apps/api/src/tasks/tasks.controller.ts
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
} from "@nestjs/common";
import { StandardSchemaValidationPipe } from "@nestjs/common";
import { z } from "zod";
import { TasksService } from "./tasks.service";
import {
createTaskSchema,
taskIdSchema,
updateTaskSchema,
} from "./tasks.schemas";
reference/TaskFlow_Business_Logic_Snapshot/apps/api/src/tasks/tasks.schemas.ts
import { z } from "zod";
import {
TaskPriority,
TaskStatus,
} from "../generated/prisma/client";
const taskTitleSchema = z.string().trim().min(1).max(200);
const taskDescriptionSchema = z.string().trim().max(2000).nullable();
export const taskIdSchema = z.uuid();
export const createTaskSchema = z.strictObject({
projectId: z.uuid(),
title: taskTitleSchema,
description: taskDescriptionSchema.optional(),
assigneeId: z.uuid().nullable().optional(),
priority: z.enum(TaskPriority).optional(),
dueAt: z.iso.datetime({ offset: true }).nullable().optional(),
});
reference/TaskFlow_Business_Logic_Snapshot/apps/api/src/tasks/tasks.service.ts
import { Injectable } from "@nestjs/common";
import {
Prisma,
TaskStatus,
} from "../generated/prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import {
AssigneeNotWorkspaceMemberException,
DueDateInPastException,
ProjectNotFoundException,
TaskNotFoundException,
TaskStatusTransitionException,
} from "../common/errors/application.exceptions";
import {
CreateTaskDto,
UpdateTaskDto,
} from "./tasks.schemas";
const ALLOWED_STATUS_TRANSITIONS: Record<TaskStatus, readonly TaskStatus[]> = {
TODO: [TaskStatus.IN_PROGRESS, TaskStatus.CANCELED],reference/TaskFlow_Business_Logic_Snapshot/tests/business-rules-smoke-tests.ps1
# Phase 14 business-rule smoke checks.
# Replace placeholders with IDs from your development seed/database.
$api = "http://127.0.0.1:4000"
$taskId = "<TASK_UUID>"
$workspaceMemberUserId = "<MEMBER_USER_UUID>"
$nonMemberUserId = "<NON_MEMBER_USER_UUID>"
# Allowed transition: TODO -> IN_PROGRESS
Invoke-RestMethod -Method Patch `
-Uri "$api/tasks/$taskId" `
-ContentType "application/json" `
-Body '{"status":"IN_PROGRESS"}'
# Allowed transition: IN_PROGRESS -> DONE. completedAt should be populated.
Invoke-RestMethod -Method Patch `
-Uri "$api/tasks/$taskId" `
-ContentType "application/json" `
-Body '{"status":"DONE"}'