MCPcopy Index your code
hub / github.com/byronka/minum

github.com/byronka/minum @v10.0.4

Chat with this repo
repository ↗ · DeepWiki ↗ · release v10.0.4 ↗ · + Follow
2,037 symbols 9,079 edges 209 files 877 documented · 43%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Minum Web Framework

This codebase provides the facilities necessary to build a web application, with a design that prioritizes server-side-rendering. Basic capabilities that any web application would need, like persistent storage or templating, are provided. See Features further down in this document.

You might find this project well-suited to your needs if your priority is maintainability and quality, and if you are aiming to build programs that last years with minimal upkeep.

There are several examples of projects built with this framework in the example projects below.

Here is a small Minum program (see more code samples below):

public class Main {
    public static void main(String[] args) {
        var minum = FullSystem.initialize();
        var wf = minum.getWebFramework();
        wf.registerPath(GET, "",
                r -> Response.htmlOk("

Hi there world!

"));
        minum.block();
    }
}

The high level characteristics

Capability Rating
Small [#######-]
Tested [#######-]
Documented [######--]
Performant [######--]
Maintainable [#######-]
Understandable [#######-]
Simple [######--]
Capable [######--]
  • Embraces the concept of kaizen: small beneficial changes over time leading to impressive capabilities
  • Has its own web server, endpoint routing, logging, templating engine, html parser, assertions framework, and database
  • Designed with TDD (Test-Driven Development)
  • It has 100% test coverage (branch and statement) that runs in 30 seconds without any special setup (make test_coverage)
  • Has close to 100% mutation test strength using the PiTest mutation testing tool (make mutation_test)
  • Relies on no dependencies other than the Java 21 (and up) SDK - i.e. no Netty, Jetty, Tomcat, Log4j, Hibernate, MySql, etc.
  • Is thoroughly documented throughout, anticipating to benefit developers' comprehension
  • No reflection - The framework's method calls are all scope-respecting, and navigation of the code base is plain
  • No annotations - unlike certain other frameworks, the design principle is to solely use ordinary Java method calls for all behavior, and configuration is minimal and relegated to a properties file
  • No magic - nothing unusual behind the scenes, no byte-code runtime surprises. Just ordinary Java
  • Has examples of framework use - see Example Projects below

Minum has zero dependencies, and is built of ordinary and well-tested code: hashmaps, sockets, and so on. The "minimalist" competitors range from 400,000 to 700,000 lines when accounting for their dependencies.

Applying a minimalist approach enables easier debugging, maintainability, and lower overall cost. Most frameworks trade faster start-up for a higher overall cost. If you need sustainable quality, the software must be well-tested and documented from the onset. As an example, this project's ability to attain such high test coverage was greatly enabled by the minimalism paradigm. The plentiful tests and comments help to make intent and implementation clearer.

Minum follows semantic versioning.

The design process

1) Make it work. 2) Make it right. 3) Make it fast.

This project represents thousands of hours of an experienced practitioner experimenting with maintainability, performance, and pragmatic simplicity.

Getting Started

There is a 🚀 Quick start, or if you have a bit more time, consider trying the tutorial

Maven

Maven central repository

<dependency>
    <groupId>com.renomad</groupId>
    <artifactId>minum</artifactId>
    <version>10.0.4</version>
</dependency>

Features:

Secure TLS 1.3 HTTP/1.1 web server

A web server is the program that enables sending your data over the internet.

In-memory database with disk persistence

A database is necessary to store data for later use, such as user accounts. Our database stores all its data in memory, but writes it to the disk as well. The only time data is read from disk is at database startup. There are benefits and risks to this approach.

Server-side templating

A template is just a string with areas you can replace, and the template processor renders these quickly. For example, here is a template: "Hello {{ name }}". If we provide the template processor with that template and say that "name" should be replaced with "world", it will do so.

Logging

Logs are text that the program outputs while running. There can be thousands of lines output while the program runs.

Testing utilities

The test utilities are mostly ways to confirm expectations, and throw an error if unmet.

HTML parsing

Parsing means to interpret the syntax and convert it to meaningful data for later analysis. Because web applications often have to deal with HTML, it is a valuable feature in a minimalist framework like this one.

Background queue processor

Across the majority of the codebase, the only time code runs is when a request comes in. The background queue processor, however, can continue running programs in parallel.

Size Comparison:

Compiled size: 209 kilobytes.

Lines of production code (including required dependencies)

Minum Javalin Spring Boot
6,715 141,048 1,085,405

See a size comparison in finer detail

Performance:

Performance is a feature. On your own applications, collect performance metrics at milestones, so that trends and missteps are made apparent.

One of the benefits of minimalism combined with test-driven development is that finding the bottlenecks and making changes is easier, faster, and safer.

  • Web request handling: Plenty fast, depending on network and server configuration. details here
  • Database updates/reads: 2,000,000 per second. See "test_Performance" in DbTests.java. O(1) reads (does not increase in time as database size increases) by use of indexing feature.
  • Template processing:
  • 100,000 per second for large complex templates.

See a Minum versus Spring performance comparison

Documentation:

Example projects demonstrating usage:

See the following links for sample projects that use this framework.

Smallest-possible

This project is valuable to see the minimal-possible application that can be made. This might be a good starting point for use of Minum on a new project.


Example

This is a good example to see a basic project with various functionality. It shows many of the typical use cases of the Minum framework.


Memoria project

This is a large and sophisticated program, about 15,000 lines of code, and is an example of the intended end-goal state of a Minum application. It is a family-tree video and sharing application.


Restaurants

Restaurants is a prototype project to provide a customizable ranked list of restaurants. On this project's README, there is a link to a video of its construction.


Alternate database

This is a project which uses a SQL database called H2, and which shows how a user might go about including a different database than the one built-in.

Code samples

The following code samples help provide an introduction to the features.

Database

Instantiating a new database:

There are two database options - the older simpler database, or the new DbEngine2 database. They have nearly identical interfaces and external behaviors, but the DbEngine2 database reads and writes to disk magnitudes faster than its sibling Db. It is the recommended database to use going forward.

Here is how you instantiate the faster database:

AbstractDb<Foo> db = new DbEngine2<>(foosDirectory, context, new Foo());

Here is the simpler database:

AbstractDb<Foo> db = new Db<>(foosDirectory, context, new Foo());

The Minum database keeps its data and processing primarily in memory but persists to the disk. Data is only read from disk at startup.

There are pros and cons to this design choice: on the upside, it's very fast and the data stays strongly typed. On the downside, it does not make the same guarantees as ACID-compliant databases. Also, an ill-considered application design could end up using too much memory.

On the Memoria project, the risks and benefits were carefully considered, and so far it has worked well.

Users are free to pick any other database they desire (See "Alternate database" project above for an example project using a third-party database).


Checksum

On the DbEngine2 database engine (the recommended engine for most users), there is a "checksum" feature. In short, a hash will be generated on data when written, and checked against the file when read. This confirms the data has not changed since it was written, which could indicate data corruption. If that is discovered, an exception will be thrown when the data is loaded. For that reason, it is recommended to use the following pattern when instantiating (note the loadData at the end):

AbstractDb<Foo> db = new DbEngine2<>(foosDirectory, context, new Foo()).loadData();

If registering indexes (see the section on indexes later on), this pattern is recommended:

AbstractDb<Foo> db = new DbEngine2<>(foosDirectory, context, new Foo())
        .registerIndex("id", x -> x.getId().toString())
        .loadData();

Adding a new object to a database:

// instantiate an object that implements DbData. Note that because
// this is a new object, we set the index to 0.
Foo myFoo = new Foo(0L, 42, "blue");

// write it to the database.  The returned value is the object we wrote
// with its index set by the database.
Foo myResultingFoo = db.write(myFoo);    

Updating an object in a database:

myResultingFoo.setColor("orange");
db.write(myResultingFoo);    

Deleting from a database:

db.delete(myResultingFoo);    

Getting a bit more advanced - indexing:

If there is a chance there might be a large amount of data and search speed is important, it may make sense to register indexes. This can be best explained with a couple examples.

1) If every item has a unique identifier, like a UUID, then this is how you would register the index for much-increased performance in getting a particular item (compared to a worst-case full table scan)

In the following example, we will have a database of "Foo" items, each of which has a UUID identifier.

// The first parameter is the name of the index, which we will refer to elsewhere when 
// asking for our data, and the second parameter is the function used to generate the key
// for the internal map.
db.registerIndex("id", x -> x.getId().toString())

// later to get the data, it will look like this, and will return
// a collection of data.  If the identifier is truly unique, then we can 
// prefer to use the findExactlyOne method
db.findExactlyOne("id", "87cfcbc1-5dad-4dcd-b4dc-7d8da9552ffc");

2) alternately, instead of having one-to-one unique values, we might be partitioning the data in some way. For example, the data may be categorized by color.

// note that the key generator (the second parameter here) must always return a string
db.registerIndex("colors", x -> x.getColor().toString())

// later to get the data, it will look like this, and will return
// a collection of data.  If the identifier is truly unique, then we can 
// prefer to use the findExactlyOne method
Collection<Foo> blueFoos = db.getIndexedData("colors", "blue");

Logging

Writing a log statement:

// useful info for comprehending what the system is doing, non-spammy
logger.logDebug(() -> "Initializing main server loop"); 

The logs are output to "standard out" during runtime. This means, if you run a Minum application from the command line, it will output its logs to the console. This is a typical pattern for servers.

The logs are all expecting their inputs as closures - the pattern is () -> "hello world". This keeps the text from being processed until it needs to be. An over-abundance of log statements could impact the performance of the system. By using this design pattern, log statements will only be run if necessary, which is

Extension points exported contracts — how you extend this code

IFileUtils (Interface)
This interface allows us to mock out many of the I/O methods of the framework for easier testing of IOExceptions. [6 implementers]
src/main/java/com/renomad/minum/utils/IFileUtils.java
IFileReader (Interface)
An interface for the operations of reading a file. This primarily exists to make testing easier. [5 implementers]
src/main/java/com/renomad/minum/utils/IFileReader.java
ISocketWrapper (Interface)
This is the public interface to ISocketWrapper, whose purpose is to make our lives easier when working with {@li [5 implementers]
src/main/java/com/renomad/minum/web/ISocketWrapper.java
IRequest (Interface)
An HTTP request. From Wikipedia A clien [5 implementers]
src/main/java/com/renomad/minum/web/IRequest.java
AbstractActionQueue (Interface)
This class provides the ability to pop items into a queue thread-safely and know they'll happen later. For example, [4 …
src/main/java/com/renomad/minum/queue/AbstractActionQueue.java

Core symbols most depended-on inside this repo

get
called by 203
src/main/java/com/renomad/minum/logging/ThrowingSupplier.java
sleep
called by 172
src/main/java/com/renomad/minum/utils/MyThread.java
logDebug
called by 133
src/main/java/com/renomad/minum/logging/ILogger.java
getLogger
called by 129
src/main/java/com/renomad/minum/state/Context.java
deleteDirectoryRecursivelyIfExists
called by 118
src/main/java/com/renomad/minum/utils/IFileUtils.java
size
called by 111
src/main/java/com/renomad/minum/utils/IFileUtils.java
add
called by 101
src/main/java/com/renomad/minum/utils/RingBuffer.java
contains
called by 85
src/main/java/com/renomad/minum/utils/RingBuffer.java

Shape

Method 1,813
Class 190
Enum 15
Interface 15
Function 4

Languages

Java100%
TypeScript1%

Modules by API surface

src/test/java/com/renomad/minum/web/WebTests.java101 symbols
src/test/java/com/renomad/minum/database/DbEngine2Tests.java100 symbols
src/test/java/com/renomad/minum/database/DbTests.java99 symbols
src/test/java/com/renomad/minum/web/RequestTests.java63 symbols
src/main/java/com/renomad/minum/web/WebFramework.java37 symbols
src/test/java/com/renomad/minum/web/WebFrameworkTests.java33 symbols
src/test/java/com/renomad/minum/htmlparsing/HtmlParserTests.java31 symbols
src/main/java/com/renomad/minum/htmlparsing/HtmlParser.java30 symbols
src/test/java/com/renomad/minum/utils/FileUtilsTests.java27 symbols
src/test/java/com/renomad/minum/web/ResponseTests.java26 symbols
src/test/java/com/renomad/minum/database/DbFileConverterTests.java26 symbols
src/main/java/com/renomad/minum/utils/FileUtils.java26 symbols

For agents

$ claude mcp add minum \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact