Pellet is a new, experimental, open-source, Kotlin-based web framework I’m working on. I’ve used numerous web frameworks in various languages over the years, and want to better understand the advantages and disadvantages of their choices — from the perspective of both their internal architecture and their developer-facing APIs. You can find the work so far here: github.com/lopcode/Pellet

Pellet logo
The Pellet logo and website

In using these frameworks, I’ve seen patterns that have been frustrating, such as the overuse of reflection in Java-based projects, resulting in code where it’s difficult to trace what happened and why. Or Go-based frameworks that tend towards the opposite extreme, where purported simplicity results in more code than should be necessary to accomplish a simple task. Or PHP frameworks that have very low barriers to entry but surprisingly poor performance and memory usage characteristics.

I appreciate that these experiences are anecdotal, and I’ve deliberately not named which frameworks were frustrating to use because that’s not really the point. In the end, I’d like to work towards having a framework that I’m comfortable using for my projects — with an API that strikes a better balance between ergonomics, performance, and stability, for my use cases. I’m not afraid of a challenge, and worst case, I’ll learn a lot about the fundamentals of HTTP. I might even inspire someone more talented than me to make the web framework of my dreams.

To get started, I sat down one afternoon and had a long think about what it was that I actually wanted from a web framework.

Goals #

These design goals are a mixture of opinions I’ve formed over time, and notes to myself to guide the implementation. They’re likely to expand and change as I make progress! I’ll be disappointed if none of them are orthogonal because I’m excited to learn where the tradeoffs lie.

With that said, my ideal framework should be:

Simple #

  • Both for developers (users of the framework), and to maintain it (myself)
  • Kotlin-first — Java interop is a bonus for my usage
  • Only target the JVM — I think cross-platform deployment is largely an unnecessary distraction
  • Reduce the size of the dependency graph where possible
  • Keep the use of annotations and reflection to a minimum

Modern #

  • Target latest stable Kotlin
  • Target latest LTS Java minimum, maybe latest stable
  • Favour proven deployment methods and patterns — e.g., the 12 factor app1
  • Favour the use of container systems like Docker
  • Use platform advancements like Coroutines, or Loom

Opinionated / use-case driven #

  • Demo/sample projects from the start, to drive implementation
  • Don’t be afraid to present a single good way of doing something
  • Favour a “three layer” architecture — api, domain, and data, with appropriate separation between them
  • Make sensible and proven choices for the basics
    • Logging (SLF4J2)
    • Metrics (OpenTelemetry3)
    • Database access (Jdbi4? — avoid ORMs that obfuscate the SQL)
    • Database migrations (TBD)
    • Messaging / message queues (TBD)

Reliable #

  • High unit test coverage
  • Integration tests
  • Real usage tested with sample projects

Fast to start up, and run #

  • Proven with benchmarks, traceable over time
  • Reflection-less

Final thoughts #

I’ve already started, and I’m having a lot of fun laying the foundations. In particular, I’ve already learned a lot about Kotlin Coroutines5 — enough to decide that they’re likely to be pervasive in the framework. I think they’re going to be especially useful for structured concurrency (such as the chaining of upstream requests, or mixing with database requests), but those benefits won’t show themselves until the framework is more fleshed out.

I’m struggling to find benchmarking tooling flexible enough to test multiple aspects of the response characteristics, such as latency and throughput of both fixed-size connection pools (i.e. processing speed) and “unbounded” connection pools (i.e. parallelism). I’d also like to be able to graph the results over time, so some sort of CSV output would be ideal. If you have any suggestions, feel free to send me a message using the contact details in the about page.


Footnotes #

  1. 12 factor app methodology - https://12factor.net/ 

  2. Simple Logging Facade for Java (SLF4J) - http://www.slf4j.org/ 

  3. OpenTelemetry - https://opentelemetry.io/ 

  4. Jdbi - https://jdbi.org/ 

  5. Kotlin Coroutines - https://kotlinlang.org/docs/coroutines-overview.html 

Streaming with a MacBook Pro - 2021 »