Library / SDK
farseer-go/fs avatar
farseer-go/fs

farseer-go: a modular Go framework ported from a decade-old .NET one

farseer-go all components of the base module, module launcher, framework initialization program(所有组件的基础模块,模块启动器,框架初始化程序)

545 stars14 forksGoMIT

At a glance

What is it?
farseer-go is a set of Go packages that aim to be the base module, the module launcher and the framework initialiser for a domain-driven application. It is a port of a .NET framework that has been in use for more than ten years, and the design decision that shapes everything is that each capability is its own module: an application that uses one package downloads one package, and the framework's own size is not something the user pays for.
Who is it for?
farseer-go fits a Go team that wants the structure a mature framework imposes, and that recognises the .NET framework it was ported from, because the design decisions carry over with their reasoning intact. It does not fit a Go team that wants a small dependency tree and would rather pick its own pieces, and it does not fit a team outside the Chinese ecosystem, where the documentation is available but the community around it is not.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 124 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the project is

farseer-go describes itself as a modular and complete infrastructure framework based on Go, for creating modern web applications and interfaces, and its stated aim is to use the most popular components available today and provide them with modular technology.

The repository's own description, in both languages, is more specific and more interesting: all components of the base module, the module launcher, and the framework initialisation program. So the repository you are looking at is the foundation, not the whole framework. The other capabilities are separate repositories, and the component table lists them with a one line description each: the core package itself, a data collections package, a web interface package, an asynchronous programming package, an object conversion package, a local cache package, and further entries that the readme truncates.

The repository topics tell you the rest of the territory, and they are a good map of what a Go application framework touches: an inversion of control container, an object mapper, an object relational mapper, queues and two message broker clients, a cache, a search engine client, a service registry, an event bus, and a scheduler.

And the lineage is stated in the readme: there is a mature version of the same framework for.NET that has been in use for over ten years, and this is the Go version of it. That fact shapes everything about the project, because a port of a ten-year-old framework is not a fresh design with Go syntax. It is a set of decisions that were made a decade ago, in a different language, in a different ecosystem, and then re-implemented.

Which is why the readme's feature list does not read like a Go framework's feature list. It reads like a set of principles, and the phrases are the.NET framework's: unified configuration, elegant, modularity, domain-driven design, and link tracking. Those are not Go idioms, they are the vocabulary of a particular.NET architecture, and carrying them across is the project's bet.

The documentation is published on a site with an English and a Chinese version, and the source repository is linked separately. The project is MIT licensed, and the release history shows a patch line in the zero series with the most recent release in the middle of 2025 and the last push on the repository in June 2026.

The five claims, and what each one is doing

The feature list has five items, and each of them is a design decision with a reason behind it. Reading them in order is the fastest way to understand the framework.

The first is unified configuration: all configurations are consolidated into a single file in the application's configuration directory. That is a strong statement. It means there is one place where every setting lives, which is exactly what an operator wants when something has to be changed at three in the morning, and it is also a strong constraint on the framework, because it means every module has to be willing to take its settings from a shared file rather than from its own source of truth. A framework that centralises configuration has to define a schema for it, and that schema is where the modules' real coupling lives.

The second is elegant, and the readme qualifies it: all modules follow the principle of developer experience first. That is a claim about what gets optimised. In a framework there is always a choice between making the code that uses the framework pleasant and making the framework itself pleasant to implement, and a framework that chooses the former will sometimes have an awkward internal design in exchange for a simple call site. Reading the claim as a priority statement is more useful than reading it as an adjective.

The third is modularity, described as a complete modular system that lets you develop reusable application modules. And the fourth is domain-driven, which the readme explains as helping you implement a hierarchical architecture based on the discipline and build a maintainable code base. These two are a pair, and they are about the same thing: a module is not a library of helpers, it is a slice of an application with its own aggregate, its own repository, its own events, and its own place in the layering. The tactical patterns the readme names, a repository in the warehousing layer, transactions in the application layer, domain events, and dynamic interfaces in the application layer, are the vocabulary of that approach.

And the fifth is the one that is most distinctive, so it gets its own section: link tracking.

The fourth and the fifth are also where the port shows most. A .NET framework of that era and market made its heavy lifting in the application layer, with an inversion of control container, an object mapper and a layer for transaction boundaries, and a Go framework that has the same shape is not copying a style, it is copying an architecture.

The dependency argument, and the package layout

The readme answers the objection that every framework faces before the reader has finished the introduction, and the answer is a single sentence: do not worry about the framework making you depend on too many packages, because the components are all separate packages and unused packages are not downloaded into your application.

That is the right answer to the right question. The usual complaint about a framework is not the number of packages in the organisation, it is the number of packages in your application's dependency file and in your build. A framework that arrives as one module with everything inside it is unusable in that sense, because you cannot take one piece of it.

So the answer is that the framework is not one package. The repository you are looking at is the base, and the capabilities are separate repositories with separate version lines, and the ones you do not import are not in your build at all. The topics confirm the shape, since the repository topics are the union of what the components do rather than the list of what this one contains.

The component table makes the structure concrete, and the naming is a small cultural detail worth noticing. The packages are named with lower case and camel case, such as the local cache package, in a language whose convention is lower case with underscores or nothing at all. Camel case in Go package names is unusual, and it is a direct carry-over from the .NET original, where the convention was capitalised words. It is the sort of detail that a reviewer would flag and a porter would defend, and it is a reasonable proxy for how much of the original is really here.

The dependency list inside the core module is the evidence for the modularity claim, and it is short. The module file for the base package requires a JSON implementation, a goroutine management library, a YAML parser, and a binary serialisation format, with two indirect dependencies for the reflection support behind the JSON one and one for the tag parsing behind the serialiser.

Read that list against the topics. There is no database driver, no cache client, no broker client, no search client and no web framework in the base module, even though the project supports all of them. Those live in their own packages, which is precisely the point: the base module is small enough that a user who wants only the container and the mapper pulls two dependencies, not forty.

And the four that are there are all about performance and serialisation. A faster JSON implementation, a binary format for messages, a YAML parser for the configuration file, and a goroutine helper for the asynchronous work. That is a coherent set of choices for a framework whose selling point is speed.

Link tracking, and the project that goes with it

The fifth feature is the one that will be either the reason you use this framework or the reason you do not, and it deserves to be read exactly as written.

If you use the framework's object relational mapper, its cache, its HTTP client, its gRPC client, its search engine client, its message queue client, its event bus, its task runner or its scheduler, the framework will implicitly implement link tracking for you and provide interface request logs and slow queries.

The word implicit is doing all the work. There is no instrumentation step, no decorator to apply, no configuration to turn on. The tracking exists because those clients are the framework's own, and they report what they do to the framework's tracing component.

That is a genuinely clever piece of design and a genuinely controversial one. The cleverness is that the cost of instrumentation is paid once, in the framework, rather than at every call site in the application, so a developer who is not thinking about observability gets it anyway. A request that goes out to a broker and then into a database and then to a search index arrives as one trace with the timings of each step, and the slow query is the slow query in that trace rather than a separate log line somewhere.

The controversy is that it is not opt-in, and that it applies to the framework's clients rather than to yours. An application that uses its own HTTP client for one call and the framework's for the rest has a trace with a hole in it. An application that wants to send data to a third party with a privacy requirement has to know what the framework records.

The companion project is what turns the feature into a product. The readme names it: automatic build, a link tracing console, and cluster log collection, and it says that together they support non-intrusive full link real-time monitoring. So the framework produces the traces, and the other project is the console you look at them in, plus the collection agent that ships them from a cluster.

That division is worth noticing. The framework is a library that embeds tracing in every client it owns, and the console is a separate application. It is a deliberate separation of the thing you import from the thing you run, and it means the tracing data model is public enough for somebody else to build a console against.

The repository, and what the directories are

The top level of the repository is a long list of small directories, and reading them is a better index of the framework than the feature list.

The obvious ones are there. There is a container directory, which is the inversion of control container. There is a mapper directory for object conversion. There is an exception directory for error handling, a logging directory, a colour directory for terminal output, a date and time directory, and a stopwatch and a timing wheel, the latter being the data structure for scheduling work at a precise future moment rather than approximately.

Then there are the ones that tell you what the framework is for. There is a trace directory, which is where the link tracking lives, and a directory for local context, which is the Go equivalent of the ambient state that a.NET framework carries in a scoped context object, and a directory for asynchronous programming, plus a local variant of it.

There is a net directory, a parse directory and a path directory, which are the utility layers, and a types directory, and a batch file writer, which suggests a bulk output path for large data sets. There is a directory for a distributed identifier generator, named after the algorithm it implements, and one for a network component whose name the readme does not expand.

And there is a directory for the module system itself, and a configure directory, and the initialisation program at the root, which is the piece the repository description calls the framework initialisation program. There is a configuration file at the root, which is the sample of the consolidated configuration the framework expects in the application, and there is a second file with the same name inside the configure directory, which is probably the schema or the defaults.

The test directories are worth a note. There is a directory whose name begins with an underscore, which is the convention for a directory excluded from the build of the source package, so the tests are run in the repository but not shipped to users. And there is a benchmark directory, which means the performance claims are at least partly measured rather than asserted.

The remaining top level files are the ordinary ones: the licence, the security policy, the module file, the readme, and the continuous integration configuration whose badge is at the top of the readme along with a coverage badge and a general report card badge. Three separate quality badges on a framework repository is a good sign, and the coverage one in particular says the framework is measured rather than assumed.

The port, and who it is for

Pulling the threads together, the interesting thing about farseer-go is not any individual component but the fact that it is a port with its reasoning attached.

The claims in the feature list are claims about architecture: a hierarchical domain-driven layout, transactions in the application layer, a repository pattern in the domain layer, domain events, and dynamic interfaces in the application layer. A framework that pushes you towards that layering is making a statement about where business logic belongs, and the statement is the one the .NET framework made a decade ago.

That is the framework's value and its limit. The value is that a team that has worked with that architecture has a map: the module boundaries, the container registrations, the transaction scopes and the event publishing all have obvious homes. The limit is that the same team working in a different ecosystem would never have chosen this layering, and the framework would be a set of constraints they did not ask for.

The documentation being bilingual is the other signal about the audience. The readme is written in a mixture of the two languages, with the English and Chinese versions of the documentation site both linked, and the Chinese text is not an afterthought: the paragraphs that explain the design decisions are the ones with the most Chinese in them, which usually means the ones that were hardest to translate or that the author preferred to write in their first language.

Which is a fair place to end. A framework that is a port from a mature .NET implementation, written by a team that clearly knows that implementation deeply, with the components split so that nobody pays for what they do not use, and with observability that comes for free because the framework owns the clients: that is a coherent and useful thing, and it is aimed at a particular audience. If you are in it, the framework removes a lot of decisions. If you are not, the component list is still a useful map of what a Go application framework is expected to contain.

Editorial conclusion

farseer-go fits a Go team that wants the structure a mature framework imposes, and that recognises the .NET framework it was ported from, because the design decisions carry over with their reasoning intact. It does not fit a Go team that wants a small dependency tree and would rather pick its own pieces, and it does not fit a team outside the Chinese ecosystem, where the documentation is available but the community around it is not. Verify first which of the modules you will actually use, since the whole value proposition is that unused ones cost nothing, that the tracing behaviour is what you want, since it is implicit rather than opt-in, and that the configuration file's format is one your deployment can generate, since everything is consolidated into it.

Frequently asked questions

What is the farseer-go project?

It is a set of Go modules for building modern web applications and interfaces on a domain-driven design, and this repository is the base module, the module launcher and the framework initialisation program. The other capabilities, the web interface, the collections, the asynchronous programming, the object mapper and the local cache among them, are separate repositories.

Where does farseer-go come from?

It is the Go version of a .NET framework that the readme says has been in use for over ten years. That is why the feature list is written in principles rather than in Go idioms, and why the architecture the framework imposes, a hierarchical domain-driven layout with transactions in the application layer and repositories in the domain layer, is one that was chosen a decade ago in another ecosystem.

How does farseer-go avoid making an application depend on everything?

Every capability is a separate package in its own repository with its own version line, and the packages you do not import are not in your build. The base module itself depends only on a fast JSON implementation, a goroutine helper, a YAML parser and a binary serialisation format, which is why the database, cache, broker and search clients do not appear there.

What does link tracking do in farseer-go?

If you use the framework's own object relational mapper, cache, HTTP and gRPC clients, search and queue clients, event bus, task runner or scheduler, tracing is implemented implicitly, and the framework provides request logs and slow queries. It is not opt-in and it applies to the framework's clients rather than to yours, so an application that mixes in its own clients has gaps in the trace.

How is configuration handled in farseer-go?

Unified: all the configuration is consolidated into a single file in the application's configuration directory, which is what an operator wants when something has to be changed quickly. The cost is that every module has to take its settings from that shared file, so the framework defines a schema for it and the modules' real coupling lives in that schema.

Official sources

  1. farseer-go/fs on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/farseer-go-fs.svg)](https://hysenlabs.com/projects/farseer-go-fs)

Was this useful?