Skip to content

mrbald/ufw

Repository files navigation

μFW - Micro Framework

ci Join the chat at https://gitter.im/mrbald-ufw/Lobby Licence -

μ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 →

Design Objectives

  • 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.

Introduction

uFW Topology

Terminology

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)

Configuration

Application configuration language is hierarchical YAML. This format gives a good representation of both application bootstrap process and the runtime structure.

Lifecycle Phases

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 type
  • start() - lifecycle_participants may/should establish required connections, spawn threads, etc.
  • up() - lifecycle_participants may start messaging others
  • stop() - opposite of start()
  • fini() - opposite of init()

Loaders

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.

Concurrency

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

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.

Trying It

μ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.

screenshot

Building

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+).

One-time setup

$ 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

Compiling

$ 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 -j

For a Release build, swap Debug for Release and conan-debug for conan-release.

Build options

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

Running tests

$ cmake --build --preset conan-debug --target unit-test

Running benchmarks

$ cmake --build --preset conan-debug --target benchmark

Installing

$ cmake --install build/Debug --prefix /path/to/prefix

The 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.

Sanitizers and the plugin model

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.

Using

TODO

Hacking

TODO

References

CMake/How To Find Libraries

Markdown Cheatsheet

Draw.io

Licensing

µ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.

About

A minimalist framework for rapid server side applications prototyping in C++ with dependency injection support.

Topics

Resources

License

Contributing

Stars

20 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors