NATS Server — Interfaces#

Interface catalog#

RaftNode#

  • Package: server
  • File: server/raft.go:40
  • Methods (53): Propose, ProposeMulti, ForwardProposal, InstallSnapshot, CreateSnapshotCheckpoint, SendSnapshot, NeedSnapshot, Applied, Processed, State, Size, Progress, Leader, LeaderSince, Quorum, Current, Healthy, Term, Leaderless, GroupLeader, HadPreviousLeader, StepDown, SetObserver, IsObserver, Campaign, CampaignImmediately, ID, Group, Peers, ProposeKnownPeers, UpdateKnownPeers, ProposeAddPeer, ProposeRemovePeer, MembershipChangeInProgress, AdjustClusterSize, AdjustBootClusterSize, ClusterSize, ApplyQ, PauseApply, ResumeApply, DrainAndReplaySnapshot, LeadChangeC, QuitC, Created, Stop, WaitForStop, Delete, IsDeleted, RecreateInternalSubs, IsSystemAccount, GetTrafficAccountName, GetWriteErr
  • Purpose: Defines the full operational contract for a NATS Raft Group (NRG) node. Covers proposal submission, snapshot management, cluster membership, health queries, leadership control, and lifecycle management. All JetStream clustering code references this interface.
  • Implementations: raft struct (server/raft.go), single implementation — the interface exists to allow clean testing via mock substitution and to provide a stable boundary between jetstream_cluster.go and the Raft engine.
  • Design quality: Extremely wide (53 methods) — this is a deliberate “god interface” for the Raft node. The breadth is justified by the complexity of Raft operations (consensus + membership + snapshot + observability), but it violates ISP and makes test doubles expensive to write. The 2026 addition of RaftNodeCheckpoint (a separate 4-method interface) shows the team has begun decomposing it.

RaftNodeCheckpoint#

  • Package: server
  • File: server/raft.go:99
  • Methods: LoadLastSnapshot() (snap []byte, err error), AppendEntriesSeq() iter.Seq2[*appendEntry, error], Abort(), InstallSnapshot(data []byte) (uint64, error)
  • Purpose: Supports asynchronous snapshot installation — a checkpoint is created from RaftNode.CreateSnapshotCheckpoint and allows installing snapshots without blocking the main Raft loop. Uses Go 1.23’s iter.Seq2 for lazy log iteration.
  • Implementations: raftNodeCheckpoint struct (internal)
  • Design quality: Well-segregated single-responsibility interface extracted from the original RaftNode. Good use of iter.Seq2 for streaming entries without materializing them.

WAL#

  • Package: server
  • File: server/raft.go:106
  • Methods (11): Type() StorageType, StoreMsg(subj, hdr, msg []byte, ttl int64) (uint64, int64, error), LoadMsg(index uint64, sm *StoreMsg) (*StoreMsg, error), RemoveMsg(index uint64) (bool, error), Compact(index uint64) (uint64, error), Purge() (uint64, error), PurgeEx(subject, seq, keep) (uint64, error), Truncate(seq uint64) error, State() StreamState, FastState(*StreamState), Stop() error, Delete(inline bool) error
  • Purpose: Abstracts the write-ahead log used by Raft for log persistence. The WAL interface is a strict subset of StreamStore — it intentionally reuses the stream message storage semantics for Raft log entries (each log entry is stored as a NATS message).
  • Implementations: fileStore and memStore — the same storage backends used for JetStream streams. This dual use (JetStream storage and Raft WAL) is a key architectural decision.
  • Design quality: Well-sized (12 methods), focused, and a natural subset of StreamStore. The reuse of stream storage for Raft log is architecturally elegant.

StreamStore#

  • Package: server
  • File: server/store.go:93
  • Methods (44): StoreMsg, StoreRawMsg, SkipMsg, SkipMsgs, FlushAllPending, LoadMsg, LoadNextMsg, LoadNextMsgMulti, LoadLastMsg, LoadPrevMsg, LoadPrevMsgMulti, RemoveMsg, EraseMsg, Purge, PurgeEx, Compact, Truncate, GetSeqFromTime, FilteredState, SubjectsState, SubjectsTotals, AllLastSeqs, MultiLastSeqs, SubjectForSeq, NumPending, NumPendingMulti, State, FastState, EncodedStreamState, SyncDeleted, Type, RegisterStorageUpdates, RegisterStorageRemoveMsg, RegisterProcessJetStreamMsg, UpdateConfig, Delete, Stop, ConsumerStore, AddConsumer, RemoveConsumer, Snapshot, Utilization, ResetState
  • Purpose: The primary storage abstraction for JetStream streams. Covers the full message lifecycle: write, indexed load (by seq, subject filter, prev/next), compaction, snapshotting, state reporting, and consumer management. A stream’s storage backend is swapped via this interface at creation time.
  • Implementations: fileStore (WAL + per-message index on disk) and memStore (in-memory circular buffer). Both are in the server package.
  • Design quality: Very wide (44 methods) — the richest interface in the codebase. The breadth is driven by JetStream’s query semantics (subject filtering, per-subject state, multi-filter pending counts) which require a rich storage API. Not segregatable without breaking callers; this represents the full “storage port” of the JetStream layer. FastState(*StreamState) as a performance variant of State() StreamState shows performance-conscious design.

ConsumerStore#

  • Package: server
  • File: server/store.go:360
  • Methods (13): SetStarting(sseq uint64) error, UpdateStarting(sseq uint64), Reset(sseq uint64) error, HasState() bool, UpdateDelivered(dseq, sseq, dc uint64, ts int64) error, UpdateAcks(dseq, sseq uint64) error, UpdateConfig(cfg *ConsumerConfig) error, Update(*ConsumerState) error, ForceUpdate(*ConsumerState) error, State() (*ConsumerState, error), BorrowState() (*ConsumerState, error), EncodedState() ([]byte, error), Type() StorageType, Stop() error, Delete() error, StreamDelete() error
  • Purpose: Stores and retrieves per-consumer delivery progress — ack floor, pending messages, redelivery counts. BorrowState() returns a zero-copy view to avoid allocation on the delivery hot path.
  • Implementations: consumerFileStore and consumerMemStore within filestore.go and memstore.go.
  • Design quality: Well-sized (13–16 methods). BorrowState() vs State() reveals the performance-conscious style: two methods for the same conceptual query, differentiated by allocation semantics.

AccountResolver#

  • Package: server
  • File: server/accounts.go:4045
  • Methods (7): Fetch(name string) (string, error), Store(name, jwt string) error, IsReadOnly() bool, Start(server *Server) error, IsTrackingUpdate() bool, Reload() error, Close()
  • Purpose: Resolves NATS account public NKeys to their JWT claims. The JWT contains the account’s permissions, stream limits, import/export mappings, and signing keys. This is how decentralized multi-tenancy is managed — the server doesn’t statically configure accounts; it resolves them on demand.
  • Implementations: MemAccResolver (test/embedded use), URLAccResolver (HTTP fetch), DirAccResolver (local directory of JWT files with inotify-style update tracking). A resolverDefaultsOpsImpl embedded struct provides no-op defaults, so implementors only override what they need.
  • Design quality: Well-designed (7 methods). The Start(*Server) method injects the server for implementations that need to subscribe to push updates. IsReadOnly/IsTrackingUpdate are discriminator methods that let the server adapt its behavior without type assertions — a pragmatic alternative to a richer interface hierarchy.

Authentication#

  • Package: server
  • File: server/auth.go:40
  • Methods (1): Check(c ClientAuthentication) bool
  • Purpose: The external authentication plugin interface. Allows third parties embedding the NATS server to inject a custom auth handler. The single Check method receives a ClientAuthentication view of the connecting client and returns allow/deny.
  • Implementations: defaultAuthImpl (internal struct using NKey/JWT/bcrypt logic in auth.go). External consumers implement this for custom auth (LDAP, custom token, etc.).
  • Design quality: Exemplary ISP compliance — a single-method interface following the Go convention. The companion ClientAuthentication interface provides the read-only view the auth handler needs.

ClientAuthentication#

  • Package: server
  • File: server/auth.go:46
  • Methods (6): GetOpts() *ClientOpts, GetTLSConnectionState() *tls.ConnectionState, RegisterUser(*User), RemoteAddress() net.Addr, GetNonce() []byte, Kind() int
  • Purpose: The read-side contract that an Authentication implementor sees when inspecting a connecting client. Decouples the auth handler from the full client struct — the auth handler doesn’t need to know about pub-sub internals.
  • Implementations: *client struct (which is the actual connecting client)
  • Design quality: Good interface segregation. The Authentication/ClientAuthentication pair is a textbook consumer-role interface split: the auth plugin sees only what it needs to decide on a connection.

Logger#

  • Package: server
  • File: server/log.go:27
  • Methods (6): Noticef(format string, v ...any), Warnf(format string, v ...any), Fatalf(format string, v ...any), Errorf(format string, v ...any), Debugf(format string, v ...any), Tracef(format string, v ...any)
  • Purpose: The server’s logging abstraction. Enables embedding the NATS server with a custom logger (e.g., zap, zerolog wrapper) without coupling to the default srvlog.Logger. The server checks io.Closer dynamically when replacing a logger to close the previous one.
  • Implementations: *srvlog.Logger (file, stdout, syslog, Windows event log backends in logger/ package). Test code uses test-specific implementations.
  • Design quality: Good. Six clearly named severity levels. Printf-style variadic signatures match Go logger conventions. The dynamic io.Closer check (rather than embedding it in the interface) keeps the interface minimal.

SubjectTransformer#

  • Package: server
  • File: server/subject_transform.go:74
  • Methods (3): Match(string) (string, error), TransformSubject(subject string) string, TransformTokenizedSubject(tokens []string) string
  • Purpose: Abstracts subject mapping transformations used in account import/export rules. A mapping rule (e.g., foo.* → bar.$1) is represented as a SubjectTransformer that rewrites subjects on message delivery.
  • Implementations: *subjectTransform struct
  • Design quality: Minimal and focused. The TransformTokenizedSubject method accepts pre-tokenized subjects (already split on .) to avoid re-tokenizing on hot paths — performance-conscious design.

DeleteBlock#

  • Package: server
  • File: server/store.go:220
  • Methods (2): State() (first, last, num uint64), Range(f func(uint64) bool)
  • Purpose: Abstracts three different encodings of deleted-sequence sets used in stream state replication: AVL seqsets (avl.Dmap), run-length encoded ranges (DeleteRange), and legacy []uint64 slices. Allows the replication protocol to iterate deleted sequences without knowing the encoding format.
  • Implementations: *avl.Dmap, *DeleteRange, DeleteSlice
  • Design quality: Clean 2-method interface following the Go “do one thing” convention. Range uses a visitor/callback pattern (rather than returning a channel or iterator) to minimize allocation — though Go 1.23 iter.Seq would be a modern equivalent.

option (internal — reload visitor)#

  • Package: server
  • File: server/reload.go:43
  • Methods (6): Apply(server *Server), IsLoggingChange() bool, IsTraceLevelChange() bool, IsAuthChange() bool, IsTLSChange() bool, IsClusterPermsChange() bool, IsClusterPoolSizeOrAccountsChange() bool
  • Purpose: Internal visitor interface for hot-reload configuration diffing. Each reloadable Options field is wrapped as an option implementor. The reload machinery collects the set of changed options, queries these discriminator methods, and applies in the right order.
  • Implementations: ~30 unexported structs, one per reloadable field (debugOption, tlsOption, clusterPortOption, etc.)
  • Design quality: Serviceable for its narrow purpose. The “is-X-change” discriminators are a code smell (open/closed violation — every new restart category needs a new method), but the pattern is simple and the file is well-contained.

Interface patterns#

  • Size distribution: Strongly bimodal. Most interfaces are small (1–7 methods: Authentication, AccountResolver, Logger, DeleteBlock, SubjectTransformer, WAL). Two are very large: RaftNode (53 methods) and StreamStore (44 methods). The large interfaces represent complete “system ports” where the entire capability of a subsystem must be surfaced.

  • Embedding: Minimal. WAL shares method signatures with StreamStore but does not embed it — the WAL is intentionally a subset. RaftNodeCheckpoint was split out of RaftNode without embedding. The resolverDefaultsOpsImpl struct provides default method implementations for embedding into AccountResolver implementors (a Go idiom for partial implementation of interfaces).

  • Implicit satisfaction: Mixed. Most interfaces are provider-defined (defined in the same file as their implementations): StreamStore, ConsumerStore, RaftNode, WAL, Logger. Two are consumer-defined (defined where they are used, not where implemented): Authentication (defined in auth.go, implemented by callers embedding the server) and ClientAuthentication (defined in auth.go, implemented by *client).

  • Stdlib interfaces used: io.ReadCloser in SnapshotResult.Reader; io.Closer dynamically checked in SetLoggerV2 when swapping loggers (allowing Logger implementors to optionally close resources). No use of fmt.Stringer or sort.Interface in the architectural core. The new iter.Seq2 (Go 1.23 iterators) appears in RaftNodeCheckpoint.AppendEntriesSeq, indicating the codebase tracks modern Go idioms.


Key abstractions#

  1. StreamStore + ConsumerStore — The JetStream storage layer’s primary extensibility seam. By defining these interfaces at the boundary between the stream/consumer logic and the file/memory backends, NATS separates “what to store” from “how to store it.” These are the only interfaces where behavioral substitution at runtime is a documented feature (memory streams for ephemeral use, file streams for durability).

  2. RaftNode — The distributed consensus abstraction that insulates jetstream_cluster.go (~10,900 lines) from the 5,100-line Raft implementation. Despite being excessively wide, it enables testing of the entire JetStream clustering layer with a mock Raft node and is the clearest architectural boundary in the codebase.

  3. WAL — Architecturally significant because it embodies the key decision to reuse stream storage as Raft log storage. fileStore and memStore implement both StreamStore and WAL, enabling a single storage engine to serve two different consumers.

  4. AccountResolver — The extensibility point for NATS’s decentralized auth model. Without this interface, the entire JWT-based multi-tenancy system would be hardcoded to a single resolution strategy. The three implementations (memory, URL, directory) cover test, cloud, and on-prem deployment scenarios respectively.

  5. Authentication / ClientAuthentication — A textbook interface pair for auth plugin extensibility. Authentication is a single-method “check” interface; ClientAuthentication is a read-only projection of the connecting client. This pair allows NATS Server to be embedded in applications with fully custom authentication backends without any internal coupling.


Interface-driven extensibility#

NATS Server uses interfaces for extensibility in exactly four areas:

  1. Storage backends (StreamStore, ConsumerStore, WAL): The file vs. memory storage decision is made at stream creation time and entirely abstracted by these interfaces. A hypothetical third backend (e.g., object storage, remote storage) would be added by implementing all three.

  2. Account resolution (AccountResolver): The server’s multi-tenancy system is pluggable at the JWT resolution layer. Operators can implement a custom resolver for integration with identity providers, secret managers, or internal auth systems.

  3. Authentication (Authentication): NATS Server is designed to be embeddable. The single-method Authentication interface is the primary hook: applications embedding the server can inject their own auth logic without forking.

  4. Logging (Logger): Similarly enables embedding — the host application can inject its own structured logger while the NATS Server’s internal logging code remains unchanged.

Notably absent from this list: message routing (no Router interface — Sublist is not abstracted), protocol handling (no ProtocolHandler interface — the client struct is hardcoded for all protocol types), and clustering transport (Raft uses NATS pub-sub directly, not an abstract transport). These omissions are intentional: the hot path must avoid interface dispatch overhead, so only subsystems where runtime substitution is genuinely needed get an interface.