Dapr — Interfaces#

Interface catalog#

actors.Interface#

  • Package: github.com/dapr/dapr/pkg/actors
  • File: pkg/actors/actors.go:87
  • Methods:
    Init(InitOptions) error
    Run(context.Context) error
    Router(context.Context) (router.Interface, error)
    Table(context.Context) (table.Interface, error)
    State(context.Context) (actorstate.Interface, error)
    Timers(context.Context) (timers.Interface, error)
    Reminders(context.Context) (reminders.Interface, error)
    Placement(context.Context) (placement.Interface, error)
    RuntimeStatus() *runtimev1pb.ActorRuntime
    RegisterHosted(hostconfig.Config) error
    UnRegisterHosted(actorTypes ...string)
    WaitForRegisteredHosts(ctx context.Context) error
  • Purpose: Defines the contract for the entire virtual actor subsystem. Consumers (the runtime and workflow engine) call through this interface, never the concrete actors struct. Sub-capabilities (routing, state, placement) are exposed as sub-interfaces returned by context-aware getter methods, ensuring callers only receive them once the subsystem is initialized and ready.
  • Implementations: actors.actors struct (the single concrete impl). Workflow engine’s wfengine depends on this interface.
  • Design quality: Broad (12 methods) but annotated //nolint:interfacebloat with deliberate intent. The design decision to return sub-interfaces rather than raw functionality is a clean “lazy-accessor” pattern — callers receive router.Interface etc. only after the subsystem is ready. The Init/Run split follows the two-phase lifecycle pattern common throughout Dapr.

channel.AppChannel#

  • Package: github.com/dapr/dapr/pkg/channel
  • File: pkg/channel/channel.go:33
  • Methods:
    GetAppConfig(ctx context.Context, appID string) (*config.ApplicationConfig, error)
    InvokeMethod(ctx context.Context, req *invokev1.InvokeMethodRequest, appID string) (*invokev1.InvokeMethodResponse, error)
    HealthProbe(ctx context.Context) (*apphealth.Status, error)
    SetAppHealth(ah *apphealth.AppHealth)
    TriggerJob(ctx context.Context, name string, data *anypb.Any) (*invokev1.InvokeMethodResponse, error)
  • Purpose: Abstracts all communication from daprd to the co-located application process. Used for pub/sub delivery, service invocation callbacks, binding triggers, health checks, and job triggers. Protocol (HTTP or gRPC) is hidden behind this interface.
  • Implementations: pkg/channel/http.Channel (FastHTTP-based), pkg/channel/grpc.Channel (gRPC-based). Chosen at startup based on --app-protocol flag.
  • Design quality: Well-segregated, small (5 methods), cohesive. Each method represents a distinct category of app interaction. The companion HTTPEndpointAppChannel interface is a single-method subset for named HTTP endpoint resources, following ISP cleanly.

resiliency.Provider#

  • Package: github.com/dapr/dapr/pkg/resiliency
  • File: pkg/resiliency/resiliency.go:92
  • Methods:
    EndpointPolicy(service string, endpoint string) *PolicyDefinition
    ActorPreLockPolicy(actorType string, id string) *PolicyDefinition
    ActorPostLockPolicy(actorType string, id string) *PolicyDefinition
    ComponentOutboundPolicy(name string, componentType ComponentType) *PolicyDefinition
    ComponentInboundPolicy(name string, componentType ComponentType) *PolicyDefinition
    BuiltInPolicy(name BuiltInPolicyName) *PolicyDefinition
    PolicyDefined(target string, policyType PolicyType) (exists bool)
  • Purpose: Returns *PolicyDefinition (circuit-breaker + retry + timeout bundle) appropriate for a given call target. Callers wrap outgoing operations with the returned policy without needing to know whether a policy is configured or what its parameters are.
  • Implementations: resiliency.Resiliency (full implementation backed by YAML/CRD config), resiliency.NoOp (no-op implementation for testing or when resiliency is disabled). The var _ = (Provider)((*Resiliency)(nil)) compile-time assertion enforces interface satisfaction.
  • Design quality: Good ISP adherence — 7 methods, each scoped to a specific call-site category (endpoint, actor pre-lock, actor post-lock, component inbound, component outbound, built-in). The two actor variants handle the fact that resiliency policy semantics differ before vs. after acquiring an actor lock.

security.Handler#

  • Package: github.com/dapr/dapr/pkg/security
  • File: pkg/security/security.go:49
  • Methods:
    GRPCServerOptionMTLS() grpc.ServerOption
    GRPCServerOptionNoClientAuth() grpc.ServerOption
    GRPCDialOptionMTLSUnknownTrustDomain(ns, appID string) grpc.DialOption
    GRPCDialOptionMTLS(spiffeid.ID) grpc.DialOption
    TLSServerConfigNoClientAuth() *tls.Config
    NetListenerID(net.Listener, spiffeid.ID) net.Listener
    NetDialerID(context.Context, spiffeid.ID, time.Duration) func(network, addr string) (net.Conn, error)
    MTLSClientConfig(spiffeid.ID) *tls.Config
    ControlPlaneTrustDomain() spiffeid.TrustDomain
    ControlPlaneNamespace() string
    CurrentTrustAnchors(context.Context) ([]byte, error)
    WithSVIDContext(context.Context) context.Context
    MTLSEnabled() bool
    ID() spiffeid.ID
    WatchTrustAnchors(context.Context, chan<- []byte)
    IdentityDir() *string
  • Purpose: Provides all security primitives (mTLS, SPIFFE/X.509 identity, trust anchor management) needed by components that establish network connections. A single Handler instance is passed to gRPC servers, gRPC dial operations, TCP listeners, and TCP dialers, adapting the SPIFFE SVID to each protocol’s credential type.
  • Implementations: Concrete implementation in pkg/security backed by dapr/kit/crypto/spiffe. The companion security.Provider interface (Run(ctx) error + Handler(ctx) (Handler, error)) acts as a factory — callers wait on Handler() until the initial SVID is fetched from sentry.
  • Design quality: Deliberately broad (16 methods, //nolint:interfacebloat). The breadth reflects that all security operations are intentionally centralized into one abstraction to prevent certificate-handling code from being scattered. The separation of Provider (lifecycle) from Handler (operations) is clean.

wfengine.Interface#

  • Package: github.com/dapr/dapr/pkg/runtime/wfengine
  • File: pkg/runtime/wfengine/wfengine.go:45
  • Methods:
    Run(context.Context) error
    RegisterGrpcServer(*grpc.Server)
    Client() workflows.Workflow
    RuntimeMetadata() *runtimev1pb.MetadataWorkflows
    ActivityActorType() string
  • Purpose: Defines the contract for the durable workflow engine. The runtime uses RegisterGrpcServer to mount the durabletask gRPC service, and Client() to return the workflow client used by universal.Universal for workflow CRUD APIs.
  • Implementations: wfengine.engine backed by dapr/durabletask-go. The engine depends on actors.Interface — workflows are built on top of the actor model.
  • Design quality: Small and focused (5 methods). Clean separation — the interface exposes only what the runtime’s composition root needs, not the full internal machinery of durabletask.

hotreload/loader.Interface and Loader[T]#

  • Package: github.com/dapr/dapr/pkg/runtime/hotreload/loader
  • File: pkg/runtime/hotreload/loader/loader.go:27
  • Methods:
    // Interface
    Run(context.Context) error
    Components() Loader[compapi.Component]
    Subscriptions() Loader[subapi.Subscription]
    
    // Loader[T differ.Resource] (generic)
    List(context.Context) (*differ.LocalRemoteResources[T], error)
    Stream(context.Context) (*StreamConn[T], error)
  • Purpose: Abstracts where component configurations come from (filesystem vs. Kubernetes operator gRPC stream). Interface provides type-specific Loader[T] instances for components and subscriptions; Loader[T] provides initial listing and a streaming change channel.
  • Implementations: Two implementations of Interface: loader/disk (inotify/fsnotify-based for standalone mode) and loader/operator (operator gRPC stream for k8s mode). The generic Loader[T] is implemented per-resource-type in each loader.
  • Design quality: Excellent use of Go generics (1.18+). The differ.Resource constraint on T ensures type safety across the loader/reconciler pipeline without code duplication. The Interface / Loader[T] two-level structure cleanly separates the “what sources are available” concern from “how do you load a specific resource type” concern.

grpc.API#

  • Package: github.com/dapr/dapr/pkg/api/grpc
  • File: pkg/api/grpc/grpc.go:79
  • Methods:
    io.Closer
    internalv1pb.ServiceInvocationServer  // embedded
    runtimev1pb.DaprServer                // embedded (100+ gRPC RPCs)
  • Purpose: Combines the internal (sidecar-to-sidecar) service invocation server with the external (app-facing) Dapr gRPC service into a single handler. Embedding the protobuf-generated server interfaces means the compiler enforces that every RPC method is implemented.
  • Implementations: grpc.api struct, which embeds *universal.Universal and adds gRPC-specific transport handling. The io.Closer addition is a local Dapr convention for lifecycle management.
  • Design quality: The interface is primarily composed of two generated proto interfaces rather than hand-written methods — appropriate for a protocol adapter layer. The separation from universal.Universal (which contains the actual logic) keeps the gRPC layer thin.

http.API#

  • Package: github.com/dapr/dapr/pkg/api/http
  • File: pkg/api/http/http.go:63
  • Methods:
    APIEndpoints() []endpoints.Endpoint
    PublicEndpoints() []endpoints.Endpoint
  • Purpose: Provides the router with two sets of endpoint descriptors: the full Dapr API surface and a restricted public health/metadata subset. The endpoints.Endpoint struct carries path, method, handler function, and middleware configuration — so the interface is really a route-table provider, not a direct handler.
  • Implementations: http.api struct, which builds endpoint lists from universal.Universal operations and HTTP-specific adapters (bindings, pubsub streaming, etc.).
  • Design quality: Intentionally minimal (2 methods) for testability. The actual complexity lives in the returned []endpoints.Endpoint, which is inspected during router setup rather than accessed through the interface.

Interface patterns#

  • Size distribution: Highly bimodal. Core subsystem interfaces (actors.Interface, security.Handler) are broad (10–16 methods) and carry //nolint:interfacebloat annotations acknowledging the tradeoff. Leaf/transport interfaces (http.API, wfengine.Interface, channel.AppChannel) are small (2–5 methods). The project prefers broad interfaces at subsystem boundaries and narrow interfaces at delegation boundaries.

  • Embedding: Protocol-level embedding is the dominant pattern: grpc.API embeds runtimev1pb.DaprServer and internalv1pb.ServiceInvocationServer (proto-generated). security.Handler does not embed but bundles related TLS/SPIFFE operations. Interface embedding for composition is rare.

  • Implicit satisfaction: Interfaces are defined by consumers in the calling package, not by providers in the implementation package — Go’s standard approach. Examples: resiliency.Provider is defined in pkg/resiliency but consumed by pkg/actors, pkg/api/universal, etc. The compile-time assertion pattern (var _ = (Provider)((*Resiliency)(nil))) is used throughout to catch drift.

  • Generics: Loader[T differ.Resource] in pkg/runtime/hotreload/loader is the clearest use of generics — parameterizes over component resource type (Component, Subscription) with a differ.Resource constraint. The actors package also uses generics in internal helpers. This reflects Go 1.21+ adoption.

  • stdlib interfaces used: io.Closer embedded in grpc.API; context.Context pervasively as a first parameter on all interface methods. No io.Reader/io.Writer at the subsystem level — the project’s boundaries are higher-level.


Key abstractions#

  1. actors.Interface — The most architecturally load-bearing interface. Everything above the actor subsystem (universal API, workflow engine, placement) goes through this. The lazy-accessor pattern (returning sub-interfaces via context-aware getters) is a sophisticated readiness management mechanism.

  2. channel.AppChannel — The fundamental language-agnostic boundary. This is the seam where Dapr stops being Go and becomes “any language.” It hides whether the app speaks HTTP or gRPC. All pub/sub delivery, service invocation, actor callbacks, and job triggers cross through this interface.

  3. resiliency.Provider — The cross-cutting resilience abstraction. Its design — returning a *PolicyDefinition rather than executing a policy — is a notable pattern: the caller decides when to apply the policy, not the provider. This enables composition (apply to a sub-call) and policy-free no-op injection in tests via resiliency.NoOp.

  4. security.Handler — The SPIFFE/mTLS surface. Its broad shape reflects intentional centralization: the alternative (scattered tls.Config construction) would be more dangerous than the ISP violation. The Provider/Handler split (lifecycle vs. operations) is the key design decision.

  5. hotreload/loader.Interface + Loader[T] — The dual-deployment abstraction. This interface pair is what allows the same daprd binary to run on a laptop (filesystem-watching) and in Kubernetes (operator-streaming) with zero conditional code in the core. The generic Loader[T] is among the most Go-idiomatic designs in the codebase.


Interface-driven extensibility#

Dapr uses interfaces for several distinct extensibility axes:

  • Component system: Component implementations (state stores, pub/sub, bindings, etc.) satisfy interfaces defined in dapr/components-contrib — an entirely separate repository. Dapr’s pkg/components/ wrappers adapt these to daprd’s internal types. This is the primary extensibility path: third parties write against components-contrib interfaces, not Dapr runtime interfaces.

  • Deployment mode: hotreload/loader.Interface with disk vs. operator implementations is a clean strategy pattern for runtime deployment environment selection. Similarly, channel.AppChannel with http vs. grpc implementations handles app protocol selection.

  • Security backends: security.Provider / security.Handler abstract over sentry-issued SPIFFE SVIDs. In theory, the trustanchors sub-interfaces from dapr/kit allow different trust anchor sources (file, static, API).

  • Testing: The NoOp pattern is used for resiliency (resiliency.NoOp) and security — callers can inject no-op or mock implementations without special test framework support. Actor sub-interfaces (reminders.Interface, router.Interface, etc.) are generated into mocks via testify/mock in pkg/actors/*/testing/ packages.