Skip to content

05. Design Patterns and .NET Techniques

Why This Module Matters

A newcomer can read this repo in two different ways.

The first way is purely mechanical:

  • there is a WPF app
  • there are services
  • there are tests
  • there are a lot of records and interfaces

That gets you through the file tree, but it does not teach much design judgment.

The second way is more valuable:

  • which responsibilities were intentionally separated?
  • which patterns are actually present?
  • which .NET techniques are pulling real weight?
  • which ideas are worth reusing in another project?
  • where is the design strong, and where is it intentionally pragmatic rather than idealized?

This module takes the second path.

It does not try to turn the repo into a catalog of pattern names. Instead, it uses the codebase to teach how patterns and .NET techniques become useful when a system has:

  • long-lived runtime state
  • background activity
  • external seams such as file access and device simulation
  • UI thread constraints
  • a need for testability and safe extension

Read This Module With These Files Open

  • src/InspectionPrototype.Application/State/AppState.cs
  • src/InspectionPrototype.Application/Services/WorkflowService.cs
  • src/InspectionPrototype.Presentation/ViewModels/MainViewModel.cs
  • src/InspectionPrototype.Application/ApplicationServiceCollectionExtensions.cs
  • src/InspectionPrototype.Infrastructure/InfrastructureServiceCollectionExtensions.cs
  • tests/InspectionPrototype.Tests/WorkflowServiceTests.cs

Those six files expose most of the design decisions this module discusses.

OOP Boundaries and Domain Modeling

The repo uses OOP in a practical .NET style, not a textbook-heavy style.

The important idea is not “everything is a class.” The important idea is:

  • important responsibilities have owners
  • stable concepts get stable contracts
  • mutable runtime behavior stays in explicit coordination code

What Lives In Domain

The InspectionPrototype.Domain project carries the stable nouns:

  • Recipe
  • ScanPoint
  • Alarm
  • Frame
  • InspectionResult
  • RunSummary
  • RunTerminalStatus

These records are intentionally small. That is a good design choice for this repo.

Why?

  • they create a shared language across layers
  • they are easy to serialize, test, and reuse
  • they do not hide workflow behavior inside large entity classes

This is a pragmatic domain model, not a fully rich DDD model.

What Lives Outside Domain

The live runtime model is mostly in InspectionPrototype.Application.State:

  • AppState
  • ActiveRunState
  • WorkflowState
  • SafetySignals
  • DiagnosticsEntry
  • SimulatorProfile

That split is worth noticing:

  • domain contracts describe stable business nouns and durable outcomes
  • application state describes the live workstation snapshot needed for orchestration

This is a healthy compromise for a machine-oriented desktop app. It avoids both extremes:

  • not everything is pushed into giant “smart” domain objects
  • not everything becomes anonymous DTOs with logic scattered elsewhere

Design Trade-Off

The upside is clarity. The downside is that a newcomer has to understand both:

  • domain contracts
  • runtime application state

That is acceptable here because the system is explicitly about runtime coordination.

MVVM and Presentation Separation

The presentation layer follows a clear MVVM shape, especially in:

  • src/InspectionPrototype.Presentation/ViewModels/MainViewModel.cs
  • src/InspectionPrototype.App/MainWindow.xaml

What The View Model Owns

MainViewModel owns:

  • UI-facing projection fields
  • observable collections
  • command bindings
  • dispatcher marshaling

It does not own:

  • workflow orchestration
  • device behavior
  • persistence logic
  • recipe file scanning

That separation is one of the stronger design choices in the repo.

Pattern In Practice

The real pattern here is not “MVVM because WPF says so.” It is:

  • presentation is a projection surface
  • application owns behavior

That means MainViewModel is intentionally thin in one important sense:

  • commands forward to IWorkflowService
  • state comes from IAppStateStore
  • command enablement is derived from CommandGuards

This is exactly the sort of separation a newcomer should imitate.

Trade-Off

Thin presentation still requires projection code, and this repo has a fair amount of it. That is normal. The cost of projection code is usually lower than the cost of leaking orchestration into the UI.

Service Abstractions and Dependency Injection

This repo uses interface-based seams heavily in the application and infrastructure boundary:

  • IMachineConnection
  • IMotionController
  • ICameraController
  • IFaultInjector
  • IRecipeCatalog
  • IRunHistoryStore
  • ISimulatorProfileService
  • IAppStateStore
  • IWorkflowService

What Pattern Is This?

It is a mix of:

  • dependency inversion
  • ports-and-adapters thinking
  • strategy-style variation through abstractions

The important point is not the label. The important point is what problem it solves:

  • the application layer should depend on stable behavior contracts, not simulator or file-system details

Where DI Is Composed

Startup composition happens in:

  • src/InspectionPrototype.Application/ApplicationServiceCollectionExtensions.cs
  • src/InspectionPrototype.Infrastructure/InfrastructureServiceCollectionExtensions.cs

These files are good teaching material because they show the actual map between:

  • application abstractions
  • infrastructure implementations
  • background hosted services
  • startup hydration behavior

Why This Matters

DI is useful here because the repo has multiple change points:

  • simulator implementations
  • file-backed storage
  • recipe catalog loading
  • profile configuration
  • test doubles

The interfaces are not there to satisfy architecture fashion. They are there because the repo actually benefits from substitutability and isolated tests.

Trade-Off

The risk with DI-heavy code is interface inflation. This repo is mostly on the right side of that line, but it is still worth teaching newcomers to ask:

  • does this abstraction isolate a real variation point?
  • or is it only adding indirection?

State Store Pattern

The strongest architectural pattern in the repo is the central state store.

Relevant files:

  • src/InspectionPrototype.Application/State/AppState.cs
  • src/InspectionPrototype.Application/Services/AppStateStore.cs
  • docs/adrs/ADR-001-use-central-app-state-store.md

Why It Exists

The app has several background producers:

  • workflow transitions
  • motion updates
  • safety signal changes
  • telemetry
  • frames
  • fault events
  • history hydration
  • recipe catalog hydration

Without one runtime truth, the UI and runtime rules would drift quickly.

What To Learn From It

The pattern here is simple but strong:

  • keep one canonical runtime snapshot
  • mutate it in one place
  • project from it outward
  • derive command legality from it

This is one of the best ideas in the repo for newcomers to study carefully.

Trade-Off

The cost is that AppState becomes broad and the workflow code becomes high-context. That is a reasonable trade for this kind of app, but it is still a trade. A state store is not automatically elegant just because it is centralized.

Persistence Boundaries

Persistence is intentionally kept behind application-defined abstractions:

  • IRunHistoryStore
  • IRecipeCatalog

The file-backed implementations live in infrastructure:

  • JSON recipe catalog
  • JSON run history store

Why This Is Good Design

This keeps the core rules clean:

  • the UI does not read files directly
  • workflow orchestration does not own file-format details
  • infrastructure can evolve without forcing broad changes through the UI

Startup Hydration Is A Nice Teaching Example

The repo also uses hosted services for startup hydration:

  • run history hydration
  • recipe catalog hydration
  • sample recipe provisioning
  • simulator profile hydration

This teaches a useful .NET pattern:

  • runtime startup often includes asynchronous preparation work
  • that work still belongs behind application or infrastructure boundaries, not inside random view-model constructors

Testing Seams

This repo is unusually good for teaching testability because the seams are visible.

The test project includes focused tests for:

  • command guards
  • workflow service behavior
  • streaming pipeline behavior
  • alarm lifecycle
  • recipe catalog logic
  • run history logic
  • simulator profiles
  • regression fixes

It also includes lightweight fakes for key abstractions:

  • fake motion controller
  • fake machine connection
  • fake frame source
  • fake run history store
  • fake recipe catalog
  • fake fault injector

What Pattern Is Being Used

This is classic seam-based testability:

  • define behavior behind stable interfaces
  • let the application layer depend on those contracts
  • substitute focused test doubles in tests

That is far more educational than a repo that only tests UI rendering or only tests integration through the whole app.

What To Learn From It

A newcomer should notice that the code is not “testable by accident.” It is testable because:

  • infrastructure is behind abstractions
  • command rules are explicit
  • state transitions are centralized
  • workflow coordination can be exercised without launching WPF

Modern C# and .NET Techniques Visible In The Repo

The repo uses several modern .NET techniques in a practical way.

Records For Stable Data Shapes

The codebase uses record types heavily for:

  • domain contracts
  • application state
  • diagnostics entries
  • simulator profiles

That is a strong fit because these types benefit from:

  • value-like semantics
  • concise declarations
  • with-expression updates for immutable snapshots

Generic Host In A Desktop App

The app uses the .NET host model even though it is a WPF desktop app.

That is useful because it brings in:

  • DI composition
  • hosted background services
  • structured startup and shutdown

This is a good example of modern .NET patterns moving beyond web apps.

Background Services

The telemetry and frame pipelines are hosted services, not ad hoc forever-loops hidden in the UI.

That is a strong design choice because it makes:

  • lifecycle clearer
  • runtime ownership clearer
  • diagnostics and startup behavior easier to reason about

Immutable Snapshot Updates

The repo updates state through immutable snapshot replacement rather than mutating scattered objects in place.

That works well with:

  • records
  • reducer-style update functions
  • projection-based UI models

CommunityToolkit.Mvvm

The presentation layer benefits from CommunityToolkit.Mvvm for:

  • observable properties
  • relay commands

This keeps view-model boilerplate low enough that the learner can focus on architecture rather than repetitive plumbing.

Patterns That Are Present, But Softly

A good engineering lesson here is that patterns often appear in softened form.

Strategy-Like Variation

Simulator profile behavior is not implemented through a textbook Strategy hierarchy, but it still expresses the same design idea:

  • behavior varies by selected profile
  • the orchestration stays mostly stable
  • variation is pushed to configuration and provider logic

Adapter-Like Infrastructure

Simulator and file-backed components act like adapter boundaries. They let the application talk to:

  • machine-like services
  • file-backed stores
  • catalog loaders

without making the application layer know those details directly.

Observer/Event Style

The runtime uses event-driven updates and state-change propagation rather than tightly coupled direct UI calls. Again, the value is the behavior shape, not whether someone names it Observer.

What To Imitate

These are the strongest design habits in the repo:

  • keep workflow behavior in the application layer, not in the UI
  • keep one canonical runtime state model
  • derive command legality from state instead of duplicating rules
  • isolate infrastructure behind application-owned abstractions
  • make persistence and simulator behavior testable
  • prefer clear, explicit coordination over clever but hidden magic
  • use records and immutable snapshot updates where they fit naturally

What To Be Careful With

These are the parts a newcomer should adopt carefully rather than blindly copying:

  • a central state store can become bloated if every idea is added without discipline
  • too many abstractions can make code harder to navigate if the variation point is not real
  • a high-context service like WorkflowService is appropriate here, but it still needs careful review to avoid becoming a god class
  • projection logic in view models can become noisy if every new feature adds formatting and collection management without restraint

The lesson is not “copy this structure exactly.” The lesson is:

  • use this structure when your system has the same kind of runtime pressure

Suggested Study Path

If you want to understand the design techniques in the best order:

  1. read Recipe.cs, Alarm.cs, and RunSummary.cs
  2. read AppState.cs
  3. read AppStateStore.cs
  4. read CommandGuards.cs
  5. read WorkflowService.cs
  6. read MainViewModel.cs
  7. read the test files for workflow, streaming, and alarms

That path moves from stable nouns to live runtime state to orchestration to UI projection to test seams.

The best next page is:

Diagram Brief

  • Title: Design patterns and engineering techniques in the workstation
  • Purpose: Show how domain modeling, MVVM, DI, state ownership, infrastructure seams, and tests fit together
  • Audience: newcomer developer learning how design ideas become code structure
  • Nodes: Domain Contracts, Application State, WorkflowService, CommandGuards, MainViewModel, DI Composition, Infrastructure Services, Test Doubles, Tests
  • Edges: domain contracts feed application state and workflow logic; DI composes application and infrastructure; MainViewModel projects application state; test doubles substitute for infrastructure seams in tests
  • Grouping: Domain modeling, presentation pattern, runtime coordination, infrastructure boundaries, testing seams
  • Caption: The repo’s design patterns are useful because they solve real coordination and change-management problems, not because they satisfy pattern vocabulary
  • Destination file path: docs/diagrams/source/course-05-design-patterns-and-dotnet-techniques.drawio

Docs-first project memory for AI-assisted implementation.