Create your own
Lesson illustration

Vertical Feature Slicing for Partition Process Discovery and API Tracing

Welcome back. In the previous lesson, you established the Local Instrumentation bounded context and its vocabulary: ProcessDescriptor, InstrumentationSession, Probe, TraceEvent, and Recording. That language gives the tool a semantic center independent of Frida objects, terminal parsing, Windows APIs, and NDJSON files.

Now the architectural question becomes practical: when you add process discovery, function interception, Win32 API tracing, and recording, where should their code live? This lesson partitions those capabilities into cohesive vertical feature slices so that each can evolve without turning the CLI into a collection of global “services” and cross-cutting technical folders.


Vertical slices organize around operator capabilities

A conventional layered layout groups code by technical role:

src/
  presentation/
  application/
  domain/
  infrastructure/

A feature such as “trace CreateFileW” then spreads across all four areas. To understand or change one operator-facing capability, you may need to open a command parser, handler, domain model, Frida adapter, agent message decoder, output renderer, and test suite located far apart.

The horizontal bands show a traditional Presentation, Application, Domain, and Infrastructure layout; the vertical bars show individual features spanning the technical concerns they actually need. In this CLI, a feature such as process discovery or recording should be understandable as a coherent slice rather than scattered across generic layers.

Vertical Slice Architecture changes the primary organizing principle:

Place the code needed to deliver one useful capability close together, and keep dependencies between capabilities deliberate and narrow.

For this application, the capabilities are stated in the language established last lesson:

  • Discover and select an authorized local process.
  • Add, inspect, and remove a generic function probe.
  • Trace a declared Windows API through the existing probe mechanism.
  • Start, stop, persist, and later replay a recording.

Those are meaningful actions an operator performs. By contrast, names such as FridaService, FileService, CommandHelpers, or TraceUtils describe technical categories, not capabilities. Such folders usually become places where unrelated features accumulate accidental coupling.

DDD and Vertical Slice Architecture Are Friends, Not Rivals

Read the section “Vertical slices organise behaviour” in this Full Stack City article. It provides the key distinction for this course: vertical slices give use cases a home, while the domain model remains responsible for domain rules.

In “Vertical slices organise behaviour,” begin at “A vertical slice should represent a useful business action” and read the core distinction. Focus on why a slice is named for an action, such as reserving seats, rather than a table, repository, or generic service.

A vertical slice is not a denial of layering. A slice still has boundaries. A command arrives from the CLI or REPL, input is validated, application code coordinates the use case, domain concepts protect their meaning, and technical adapters perform I/O. The difference is that these collaborators are grouped and owned according to the capability they deliver.

This distinction matters for your Frida tool because its features have very different rates of change:

  • Process discovery will likely evolve with filtering, sorting, and target-selection ergonomics.
  • Interception will evolve with native signatures, diagnostics, and probe lifecycle rules.
  • API tracing will evolve as you add declarative Win32 trace specifications.
  • Recording will evolve with redaction, event size limits, buffering, and replay.

Forcing all of those changes through a single central InstrumentationService would couple them unnecessarily.


Cohesion first, then dependency direction

Cohesion asks whether the parts of a module naturally belong together. A strongly cohesive process-discovery feature contains code that contributes directly to discovering or selecting processes. It should not also contain recording-file logic merely because both happen to use Frida.

Coupling asks how much one module depends on another. You want low coupling between features, but not artificially low cohesion inside a feature. It is healthy for the list-processes handler, its input model, Frida-backed process provider, formatter, and tests to be closely related. It is unhealthy for list-processes to know details of agent scripts or NDJSON writing.

Vertical Slice Architecture, not Layers!

Watch “Vertical Slice Architecture, not Layers!” by CodeOpinion for a concise treatment of cohesion and a concrete refactoring from technical folders to feature-based folders.

Start with cohesion, which distinguishes grouping around data from grouping around behavior. Then watch feature refactoring, where related request components are moved next to one another. Translate the example from HTTP endpoints to this tool’s CLI and REPL commands.

A useful test for a proposed slice is this:

  1. Can you name it as an operator-facing action or capability?
    ProcessDiscovery and Recording pass. Utilities and Managers do not.

  2. Would one change normally affect most files within it?
    Adding a process-name filter should mostly affect process discovery. Adding a registry API specification should mostly affect API tracing.

  3. Can it expose a small, domain-language contract to other slices?
    API tracing can ask interception to install a probe without depending on Frida’s Interceptor object or internal registry.

  4. Does it own a distinct kind of decision?
    Interception owns generic probe management. API tracing owns the meaning of named Win32 API specifications. Recording owns durable capture policy.

The slices are not isolated islands. They share the Local Instrumentation vocabulary and cooperate through explicit contracts. The goal is not “zero dependencies”; it is dependencies that correspond to real domain relationships rather than convenience imports.


Partition the first version of the tool

The following is a practical slice map for the first usable Windows-focused version.

Feature sliceOperator capabilityOwnsMust not own
Process DiscoveryList processes, filter results, and select a target PIDDiscovery query, process snapshots, deterministic filtering and sorting, target-selection behavior, process-provider adapterFrida attachment lifetime, native hooks, recording-file writes
Instrumentation SessionAttach to or detach from the selected target and inspect session statusThe user-meaningful session boundary and its link to a target processCLI tokenization, individual API definitions, persistent recordings
Function InterceptionAdd, list, and remove generic function probesProbe identity and registry behavior, module/export resolution, signature-driven interception, generic interception diagnosticsA catalog of Win32 APIs, storage format, terminal rendering
Windows API TracingTrace a named API such as CreateFileWDeclarative API specifications, Windows-specific argument and return decoding policy, the trace-api use caseA second independent hook registry or duplicate Interceptor implementation
RecordingStart/stop capture and inspect a replay summaryRecording lifecycle rules, accepted-event intake, redaction policy, durable append behavior, replay summariesRaw agent message parsing, probe installation, arbitrary terminal state

The Instrumentation Session slice is included because it is a coherent capability in its own right. It provides the context in which interception and recording operate. It should not become a god object that implements every operation performed during a session.

Process Discovery

Process discovery begins with a query that returns immutable ProcessDescriptor snapshots. A list-processes use case can accept filters and sorting options, invoke a provider, and return a deterministic result. A related select-target use case turns a chosen ProcessId into the target role used by later session work.

Its core boundary is simple:

  • It discovers what is currently observable.
  • It helps the operator choose a target.
  • It does not attach to that target or assume the target remains alive.

This protects the meaning established in the last lesson: a process descriptor is a snapshot, not a live controller.

Function Interception

Function interception is the generic mechanism slice. It owns the lifecycle of probes such as:

  • Resolve an exported function from a named loaded module.
  • Install an observation-only interceptor.
  • Decode a declared native signature safely.
  • Emit a typed TraceEvent.
  • Remove a probe idempotently.

The key word is generic. This slice should understand a declared module, export name, signature, and decoding plan. It should not be filled with special knowledge about every Windows API you may trace.

For example, a request to observe a project fixture’s exported function belongs here. So do add-probe, list-probes, and remove-probe. These are stable instrumentation capabilities even if you later add dozens of API specifications.

Windows API Tracing

Windows API tracing is intentionally separate from generic interception because its primary reason to change is different. It owns meaningful named API observations: which DLL exports the API, how arguments should be interpreted, what constitutes a safe string read, and which return information is useful.

A trace-api CreateFileW command should not independently install a Frida hook. Instead, API tracing should:

  1. Resolve CreateFileW to a declarative trace specification.
  2. Translate that specification into a generic probe request.
  3. Ask the interception capability to manage the probe.
  4. Present the resulting probe identity and diagnostics in API-tracing language.

This is an example of intentional dependency. API tracing depends on the generic probe capability because an API trace is a specialized probe. The reverse dependency would be wrong: generic interception should not import a growing catalog of Win32 APIs.

Recording

Recording starts only after the host has accepted an event as a valid TraceEvent. The recording slice receives domain-level observations, applies its own policy, and makes them durable.

It therefore owns questions such as:

  • Is this instrumentation session currently recording?
  • Which session identity and sequence information belong with an entry?
  • Does the event need redaction before persistence?
  • Has the size limit been exceeded?
  • How is an accepted entry appended and later replayed?

It does not receive raw Frida send() payloads. Raw transport input will later be validated at the host-agent boundary. Recording should consume a stable domain event shape, so Frida message-protocol changes do not force changes in file-writing logic.


A TypeScript-oriented project layout

Do not treat this layout as a rigid framework. It is a map of ownership. The exact number of files per use case should grow with complexity; one small query may be a single module, while interception will need several.

src/
  domain/
    local-instrumentation/
      identifiers.ts
      process.ts
      probe.ts
      trace-event.ts
      recording.ts

  features/
    process-discovery/
      list-processes/
        query.ts
        handler.ts
        result.ts
        handler.test.ts
      select-target/
        command.ts
        handler.ts
        handler.test.ts
      process-provider.ts
      frida-process-provider.ts

    instrumentation-session/
      attach/
      detach/
      get-session-status/
      session-store.ts

    function-interception/
      add-probe/
      list-probes/
      remove-probe/
      probe-registry.ts
      interception-agent.ts

    windows-api-tracing/
      trace-api/
      trace-specifications/
        create-file-w.ts
        registry-query.ts
      trace-api.test.ts

    recording/
      start-recording/
      stop-recording/
      replay-recording/
      recorder.ts
      ndjson-recorder.ts

There are two important ideas in this sketch.

First, a feature can contain multiple use cases. process-discovery is a feature set containing list-processes and select-target. Likewise, function-interception contains the related commands for managing a probe registry.

Second, interfaces and implementations should usually live near the slice they serve. A ProcessProvider contract belongs with process discovery because it exists specifically to support process discovery. Its Frida-backed implementation belongs there too, while keeping Frida types out of the handler’s public request and result models.

Some concepts may be genuinely shared:

Appropriate shared conceptWhy it may be shared
Branded IDs such as ProcessId, ProbeId, and RecordingIdThey preserve one domain meaning across features.
TraceEvent variantsInterception produces them; recording consumes accepted ones.
Typed Result and application error conventionsThey are engineering primitives established in Module 1.
Session identity and selected target conceptsSeveral features need the same user-meaningful context.

Avoid creating a shared/ directory for anything that has not proved genuinely shared. A generic TraceService is especially risky: discovery, interception, API tracing, and recording may all be shoved into it until it becomes the architecture’s central dependency.


Keep the slice boundary distinct from the domain boundary

Vertical slicing and DDD complement one another, but they answer different questions:

ConceptMain question
Vertical feature slice“Which code works together to deliver this operator capability?”
Domain model“Which concepts, invariants, and facts preserve the meaning of Local Instrumentation?”
Application boundary“How does this use case coordinate work and return a typed outcome?”
Technical adapter“How does a feature communicate with Frida, the terminal, or the filesystem?”

For example, add-probe is a vertical use case. Its handler coordinates inputs, probe registration, and an adapter capable of installing the hook. But the definition of a Probe, its identity, and rules such as “a probe ID identifies one requested observation” belong to the domain model.

Similarly, start-recording is a use case, but the idea that a Recording belongs to one InstrumentationSession is domain meaning rather than handler bookkeeping.

A handler should therefore be an orchestrator, not a procedural dumping ground. In this application, avoid logic such as a trace-api handler manually reaching into a Frida session, attaching an interceptor, serializing JSON, opening a file, and printing colored output. Even if that works initially, it creates a change hotspot where unrelated policies become inseparable.

Instead, let the handler coordinate specialized collaborators that remain within or immediately adjacent to its slice. The next lesson will formalize the interfaces, or ports, that allow those collaborators to remain independent of Frida, terminal I/O, and file storage.


Record the slice map before creating folders

Create docs/architecture/slice-map.md now. It should be short enough to guide implementation decisions, rather than a document that goes stale.

Capture four decisions:

  1. Feature ownership
    Copy the five-slice table and adapt names only if your terminology demands it.

  2. Allowed collaboration
    Record that Windows API tracing may use the generic probe-management capability, and recording may consume accepted TraceEvent values. State that neither feature receives raw Frida transport objects.

  3. Explicit non-ownership
    Note that process discovery does not attach, interception does not persist, API tracing does not maintain a second hook registry, and recording does not decode agent messages.

  4. A dependency rule
    Feature internals should not be imported directly by another feature. If another feature needs a capability, expose a narrow, domain-language contract rather than importing its Frida adapter or mutable registry.

This artifact will help when the CLI and REPL are introduced. Both presentation modes should invoke the same feature handlers; neither should dictate where a feature’s application logic lives.


Key takeaways

Vertical Slice Architecture organizes the codebase around cohesive operator capabilities, not generic technical folders. For this tool, the useful initial slices are:

  • Process Discovery for listing, filtering, sorting, and selecting processes.
  • Instrumentation Session for attach, detach, and status as a user-meaningful capability.
  • Function Interception for generic probe management and safe observation.
  • Windows API Tracing for named, declarative Win32 tracing built on generic probes.
  • Recording for durable, policy-controlled capture and replay.

A slice may contain domain concepts, handlers, adapters, and tests, but it should own one reason to change. Shared code must represent stable shared meaning, not a convenient dumping ground. Most importantly, API tracing may build on interception, and recording may consume accepted trace events, while each retains ownership of its own decisions.

Next, you will define application ports so Frida, terminal I/O, and file storage remain outside the domain and application logic while still serving these vertical slices.

Can't find a good explanation? Sign up and we'll make it for you

Sign up