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.cssrc/InspectionPrototype.Application/Services/WorkflowService.cssrc/InspectionPrototype.Presentation/ViewModels/MainViewModel.cssrc/InspectionPrototype.Application/ApplicationServiceCollectionExtensions.cssrc/InspectionPrototype.Infrastructure/InfrastructureServiceCollectionExtensions.cstests/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:
RecipeScanPointAlarmFrameInspectionResultRunSummaryRunTerminalStatus
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:
AppStateActiveRunStateWorkflowStateSafetySignalsDiagnosticsEntrySimulatorProfile
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.cssrc/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:
IMachineConnectionIMotionControllerICameraControllerIFaultInjectorIRecipeCatalogIRunHistoryStoreISimulatorProfileServiceIAppStateStoreIWorkflowService
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.cssrc/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.cssrc/InspectionPrototype.Application/Services/AppStateStore.csdocs/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:
IRunHistoryStoreIRecipeCatalog
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
WorkflowServiceis 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:
- read
Recipe.cs,Alarm.cs, andRunSummary.cs - read
AppState.cs - read
AppStateStore.cs - read
CommandGuards.cs - read
WorkflowService.cs - read
MainViewModel.cs - 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.
Related Lessons
- 15. OOP in Real-World .NET
- 16. SOLID Principles in .NET
- 19. Design Patterns in .NET
- 26. Domain Modeling in .NET
- 06. MVVM in Industrial Systems - Real World
- 07. DI in Industrial WPF Systems - Real World
- 13. Testability in .NET Systems - Real World
Read Next
The best next page is:
Diagram Brief
Title: Design patterns and engineering techniques in the workstationPurpose: Show how domain modeling, MVVM, DI, state ownership, infrastructure seams, and tests fit togetherAudience: newcomer developer learning how design ideas become code structureNodes: Domain Contracts, Application State, WorkflowService, CommandGuards, MainViewModel, DI Composition, Infrastructure Services, Test Doubles, TestsEdges: domain contracts feed application state and workflow logic; DI composes application and infrastructure; MainViewModel projects application state; test doubles substitute for infrastructure seams in testsGrouping: Domain modeling, presentation pattern, runtime coordination, infrastructure boundaries, testing seamsCaption: The repo’s design patterns are useful because they solve real coordination and change-management problems, not because they satisfy pattern vocabularyDestination file path:docs/diagrams/source/course-05-design-patterns-and-dotnet-techniques.drawio