μFW is a minimalist framework for rapid server side applications prototyping and experimental work on Unix-like operating systems, primarily Linux and macOS. Those familiar with Spring or Guice may experience a strong deja-vu — dependency injection support was one of the →
- Minimum number of lines of code — clean concepts, compact implementations
- Modularity in spirit of inversion of control
- Late binding support via shared library-based plugins
- Consistency in module configuration, lifecycle, concurrency, and logging
- Singleton-free design with traceable dependencies
- Structured configuration reflecting both the application topology and the concurrency model
- Zero steady state runtime overhead
- Hacking-friendly design
Following core C++ design principles, the rule "you don't pay for what you don't use" is adhered to where possible.
The terminology used in the framework maps directly to the main building blocks, which are
- entity - an identifiable building block of the application (a module)
- application - container of entities
- loader - an entity capable of loading other entities
- lifecycle_participant - an entity with application managed lifecycle
- execution context - set of rules for code execution (e.g. a specific thread, a thread pool, a strand on a thread pool, ...)
- launcher - a binary (ufw_launcher, the entry point into an application)
Application configuration language is hierarchical YAML. This format gives a good representation of both application bootstrap process and the runtime structure.
Application modules are created in the order they are defined in the configuration file and are destroyed in the reverse order.
Modules can opt to participate in the structural lifecycle by extending the virtual ufw::lifecycle_participant base.
The lifecycle phases lifecycle_participant-s are transitioned through are below.
The order of transition among individual participants matches their declaration order in the application configuration file.
init()- lifecycle_participants may/should discover and cache strongly typed references to each other and fail fast if anything is missing or is of a wrong typestart()- lifecycle_participants may/should establish required connections, spawn threads, etc.up()- lifecycle_participants may start messaging othersstop()- opposite ofstart()fini()- opposite ofinit()
A subset of entities capable of loading other entities is called loaders.
A loader entity extends the virtual ufw::loader base.
loaders are entities.
loaders can load other loaders.
A special "seed" loader — the default_loader, is used by the application to load entities by name (including other loaders).
Whether or not an entity is loaded with a loader is specified in the config (flexibility!).
entities can be registered in the application programmatically without loaders.
The application registers the default_loader in directly in the constructor.
The default launcher registers LIBRARY (loads shared libaries) and PLUGIN (loads entities from shared libraries) loaders before initiating the application bootstrap.
Application initialisation is done single-threaded in the application main thread.
Once the application is up the main thread becomes the host of the default execution context.
The default execution context an instance of the boost::asio::io_context accessible from entities via this.context().
All other concurrency models are incremental to the ufw.application.
Logging is part of the framework, backed by Quill (asynchronous, formatting on a backend thread, fmt-style format strings).
Each entity owns a named logger; the logger name is rendered in the log line via Quill's %(logger) pattern token.
Macros LOG_DBG / LOG_INF / LOG_WRN / LOG_ERR expand to QUILL_LOG_* and resolve get_logger() via unqualified lookup, so they pick up the entity's own logger inside member functions and a process-wide root logger elsewhere.
The logger is configured the same way as any other entity.
The config block is decoded into a typed logger_config { severity, pattern, timestamp_pattern }.
See the configuration file fragment below as an example.
μFW comes with an example module packaged into a plugin shared library (libexample.so on Linux).
The below configuration fragment has a single instance of the example module.
---
application:
entities:
# ====== logger ======
- name: LOGGER
config:
severity: info
pattern: "%(time) | %(log_level:<7) | %(thread_id) | %(logger) - %(message)"
timestamp_pattern: "%H:%M:%S.%Qms"
# ====== a dynamic library ======
- name: example_lib
loader_ref: LIBRARY
config:
filename: libexample.so # libexample.dylib on macOS
# ====== an entity -- plugin from a dynamic library ======
- name: example_plugin
loader_ref: PLUGIN
config:
library_ref: example_lib
constructor: example_ctor
...To run it, store the above fragment into a YAML file (say config.yaml) and run the μFW launcher as ufw_launcher -c config.yaml.
The console log should look similar to the below screenshot.
Dependencies are managed via Conan 2. The toolchain requirements are CMake ≥ 3.27 and a C++23-capable compiler (gcc 13, clang 18, or Apple clang 15+).
$ pip install 'conan==2.7.*'
$ conan profile detect --force
# Bump cppstd in the detected profile to gnu23 (the project is C++23):
$ sed -i.bak 's/^compiler.cppstd=.*/compiler.cppstd=gnu23/' ~/.conan2/profiles/default$ git clone https://github.com/mrbald/ufw.git && cd ufw
$ conan install . -s build_type=Debug --build=missing
$ cmake --preset conan-debug
$ cmake --build --preset conan-debug -jFor a Release build, swap Debug for Release and conan-debug for conan-release.
| Option | Default | Effect |
|---|---|---|
UFW_ENABLE_SANITIZERS |
ON in Debug, OFF |
AddressSanitizer + UndefinedBehaviorSanitizer on all targets |
UFW_ENABLE_CLANG_TIDY |
ON |
Run clang-tidy on ufw_app / ufw_topics (silently skipped if clang-tidy is not in PATH) |
UFW_USE_CCACHE |
OFF |
Use ccache as the compiler launcher when available |
$ cmake --build --preset conan-debug --target unit-test$ cmake --build --preset conan-debug --target benchmark$ cmake --install build/Debug --prefix /path/to/prefixThe install ships UfwConfig.cmake / UfwConfigVersion.cmake / UfwTargets.cmake, so downstream consumers can find_package(Ufw) and link the namespaced targets ufw::ufw_app, ufw::ufw_topics, etc.
The framework loads plugins via dlopen, so AddressSanitizer must be present (or absent) consistently across the launcher and every plugin shared library. The UFW_ENABLE_SANITIZERS option enforces this uniformly across all first-party targets. On macOS, Apple clang embeds the toolchain rpath that points at libclang_rt.asan_osx_dynamic.dylib, so no DYLD_LIBRARY_PATH workarounds are needed. On Linux, the asan runtime is resolved at link time via -fsanitize=address on the launcher.
TODO
TODO
µFW is dual-licensed:
- AGPL-3.0-only — free for any use where your application as a whole complies with the AGPL (including its network-interaction terms). Hobby, research, and open-source use fit here with no friction.
- A commercial license for building proprietary applications without AGPL obligations — see COMMERCIAL.md.
Contributions are accepted under Apache-2.0 inbound + DCO — see
CONTRIBUTING.md. Historical releases up to the git tag
apache-final remain Apache-2.0.

