Architecture

Isolating CGO in a sidecar to keep a Go desktop backend portable

How Laras keeps its Go API free of CGO by moving offline speech recognition into a separate sidecar process, and what that buys at build time.

By · Published

Laras is a desktop application with a Go backend running locally. Two of its requirements pull in opposite directions. It needs a database and it needs offline speech recognition. One of those can be pure Go. The other cannot.

The problem with one binary

The obvious design is a single Go server that opens SQLite and calls the speech recogniser directly. The trouble is what that does to the build.

The usual SQLite driver for Go wraps the C library, and Vosk is a C++ library with Go bindings. Both mean CGO. Once CGO is on, cross-compiling stops being a matter of setting GOOS and GOARCH. Every target needs a matching C toolchain, the Windows build has to use the same compiler family the prebuilt speech library was built with, and CI images grow to carry all of it.

That cost would be paid by the whole backend, including the large majority of it that is plain request handling and has nothing to do with audio.

Split along the dependency, not the feature

Laras draws the line where the native dependency is.

The API server is CGO-free. It uses modernc.org/sqlite, a pure-Go translation of SQLite, through the standard database/sql interface. Authentication, lessons, progress, quizzes and everything else compile with the Go toolchain alone.

Speech is a second binary. The recogniser is built as its own small program with CGO enabled and the Vosk library linked in. Tauri launches both programs as sidecars next to the window.

The main server treats speech as something it asks for, not something it contains.

What this buys

  • The common build is simple. Running the backend or its tests needs Go and nothing else. No model download, no compiler setup.
  • Toolchain rules apply to one place. The requirement to match the MinGW toolchain on Windows is real, but it is confined to one binary with a narrow job.
  • Failure can be contained. A problem in the native speech code is a problem in a separate process, not a crash in the server that holds accounts and progress.
  • The seam is reusable. Because the server already talks to speech across a process boundary, replacing the engine is a change to one program.

What it costs

There are now two artefacts to build, sign and ship, and a process boundary to cross for every recognition request. For speech that overhead does not matter: a learner speaks for several seconds, and a few milliseconds of local inter-process communication disappear next to that.

It would matter for something called thousands of times a second. This is a pattern for heavy, infrequent native work, not a general rule.

When to use it

Reach for a sidecar when a native dependency is needed by one feature, when that dependency complicates the build for everything else, and when calls to it are coarse enough that a process hop is cheap. If all three hold, isolating it keeps the rest of the codebase boring, which is what you want from the part that handles accounts and data.

The full architecture is described on the Laras project page.

Have a problem software could solve?

Describe it in a few sentences on WhatsApp. You get a direct reply about scope, platform and a sensible first version.

Request a Project