Asko Nõmm

3 min read

Thoughts of a completely modular, contract-driven system

I've been pondering lately about modern frameworks and systems, and how coupled they are, and how they constrain you into a neatly crafted box. Yet, I've rarely seen a system that fits in a box. Most things evolve, requirements change, and suddenly that box becomes a limitation, a liability, often causing a complete re-write or abandonment of the system.

Is there a way to architect a framework which functions more like a lego and less like a box? One where you can change the pieces around to form new constraints, replace things, change things?

What if, let's say in the scope of a website, routing wasn't a linear map of routes corresponding to functions, but routes were actions dependent on a contract which is molded by the user?

Instead of:

app.get('/', () => console.log('hello, world'));

You'd do something like

subscribe('http.request', (payload) => {
    if (payload.method === "GET" && payload.path === "/") {
        dispatch('http.response', ...);
    }
});

Then the core system itself really just deals with listening to events, and doing something as a result of those events. Though, if the system itself decided what it would do with those events, then it again starts to couple the whole thing into a box, so it's you, the user, who decides.

subscribe('http.response', (payload) => {
    // decide what you want to do with this
});

The above is a over-simplification of my thoughts, but should paint an idea of a fully modular, extendable system. Of course, the system should come with many built-in event resolvers, so you wouldn't have to reinvent the whole wheel each time, but that you could, if you wanted to.

Contract-driven

How do you make sure that each part fits together? How do you ensure that wrong parts don't try to communicate and blow the whole thing up in unexpected ways? Well, perhaps with contracts.

Each subscription should themselves determine what is the acceptable payload they get, and if they do not get that, they fail.

It could look something like this:

const homeRequest = {
    validator: (payload) => {
        // Validate that the payload is correct
        return true;
    },
    resolver: (payload) => {
        // Do something with the payload.
    }
};

subscribe('http.request', homeRequest);

Now you can dispatch events to it, and the subscribe function will run the validator, and discard the listener if validation does not pass. You could abstract the validator to a reusable function, thus making a contract stating that a HTTP request must correspond to X contract.

Perhaps the helper could be something like:

const isHttpRequest = (method, path) => {
    return (payload) => {
        return payload.method === method && payload.path === path;
    }
}

Which you then use as:

const homeRequest = {
    validator: isHttpRequest("GET", "/"),
    resolver: (payload) => {
        // Do something with the payload.
    }
};

subscribe('http.request', homeRequest);

Debugging

One of the nice things about an entirely event-driven system is that you can log every single thing happening, since it all goes through a centralized dispatcher, which would greatly improve your ability to understand what part does what, and what data goes where.


Perhaps I'll build a prototype of this and see what it feels like.

3 min read

Desktop Apps are Too Complex

I've been wanting to re-write Invobi for a bit now, since it currently runs on a make-shift PHP framework I made myself, and while there's nothing wrong with it - I want something a little more maintainable and future-oriented than that.

One avenue I've explored is just making it a desktop app. I already did it once as a MacOS app, but since I do not use MacOS anymore ... well, that's not very useful. I've looked into a few different multiplatform systems such as Flutter and Kotlin Multiplatform and ... holy crap. What happened there?

Kotlin Multiplatform has to download an entire planet onto my computer to be able to function, and even then it .. well, doesn't. The vanilla, out of box Desktop Multiplatform project does not run. I do not know enough Kotlin or Java mambojambo to know why, and I can't be bothered to learn either. I just wanted to get a quick feel for it, and the feel I got was ... nah, thanks.

Configuration cache state could not be cached: field `arguments` of `org.gradle.process.internal.ProcessArgumentsSpec` bean found in field `argumentsSpec` of `org.gradle.process.internal.DefaultJavaExecSpec` bean found in field `javaExecSpec` of task `:composeApp:desktopRunDev` of type `org.jetbrains.compose.reload.ComposeDevRun`: error writing value of type 'java.util.ArrayList'
> Cannot query the value of task ':composeApp:desktopRunDev' property 'className$hot_reload_gradle_plugin' because it has no value available.

Mind you, I haven't done anything. This is vanilla IntelliJ Ultimate, with a vanilla Kotlin project. I've changed no lines of code. If you can't even make your "hello world" project function then I have very little confidence in it being something that I'll be able to easily maintain for many years. Oh and, Kotlin Multiplatform IntelliJ plugin doesn't even work on Windows, so I had to use some project generation website to download a .zip of a sample project, like it's 1995 all over again!

Flutter actually worked on the first try, to my surprise. It did require me to download the hefty Visual Code (No, not VS Code), but it's for the C++ support so fine whatever. The built in dev-tools are super nice, live reload, everything. That said, I did get the feeling like it's ... very big, and again not really sure how easy it is to maintain that after many years.

I thought that desktop apps are easy - easier than web, at least - but that does not seem to be the case. Cross-platform desktop app development seems to be far more complex and convoluted than simply making a web app. Sure, a web app might cost me more (emphasis on might, because for cross platform desktop apps I probably have to shell out hundreds a year for their respective app stores), but I can build it with super simple tech like PHP, SQLite, a dash of JS for interactivity, and have it run with minimal changes for a decade.

I guess this is a long way of saying that desktop apps are way too complex, and I completely understand why everything's a web app these days.

3 min read

Dompa for Zig

Whenever I learn a new language, I do so by doing. No books, no blogs, no videos. I get a project I want to build, I go to the language's website, open up the docs, and start writing. My favorite project to do when learning a new language is to build a HTML document parser. I've written one for Rust and another one for Python.

I find it's easy enough that I won't ever need to spend more than a weekend on it (usually one evening is enough) and yet complex enough that I go through many crucial parts of what makes up the language to form an opinion and get a general feeling about it.

Recently, I was learning Zig, and thus, I created a HTML parser for Zig called Dompa. It parses a string of HTML into a tree of nodes, provides an API to traverse said tree and manipulate it programmatically, and also to then transform the tree of nodes back to an HTML string.

Use it in your Zig project:

Fetch the dependency:

zig fetch --save git+https://git.sr.ht/~asko/dompa

Import it in your build.zig:

const dompa = b.dependency("dompa", .{
  .target = target,
  .optimize = optimize,
});

// Add Dompa to lib
lib.root_module.addImport("dompa", dompa.module("dompa"));

// Add Dompa to exe
exe.root_module.addImport("dompa", dompa.module("dompa"));

Conclusion

Learning Zig was a bit of a challenge namely because it has pretty non-existent docs, especially around its new build system that allows for packages, but aside from that I really like the ergonomics of the language. It's not memory safe like Rust, but I don't get the same sweaty palms I get when writing C because Zig has made manual memory management very straight forward, and unlike C it has pretty decent stack traces if you do mess up somehow. Whilst I'm not a fan of Rust's syntax, I do really like Zig's. Overall it seems to be a promising thing.

I definitely still have some finishing touches I want to do to Dompa, such as instead of returning a std.ArrayList(Node) I would actually return my own struct so that I could provide more convenient API's (and deinit), and since I like the language so much perhaps I will also work on some other library in Zig. One such example I sometimes write is a templating language, which uses my own HTML parser, and always greatly enhances my learning experience because then I learn how library deployment and usage really works, and how nice or bad that whole thing is.

1 min read

ITYPD's month in review #1

I wrote a bit about the work that got done in June, and work that is planned in the near future.

ITYPD's first month has passed and a tremendous amount of work got done in that time. We went from having no design and barely any functionality for the blogs themselves to having fully functioning and (if I do say so myself) nice looking blogs. Oh and the app itself had no design as well, not to mention that most things did not work.

Check out the full post.

1 min read

On the new Apple software design

Before the WWDC event yesterday, the internet was buzzing with rumors that Apple might bring back skeuomorphism, and many people, myself included, rejoiced. Instead what we got was a weird Windows Vista, but somehow with worse readability. It's like the designers closed themselves off into a private room and heavily masturbated to the most shiny design candy they could think of, completely disregarding decades of UX design work and research.

Needless to say, I've been looking to go away from Apple for a while now, mainly because I have a 1.2 thousand EUR phone which does the exact same things a 400 EUR phone does. I have no use for it being Titanium, which I put in a case anyway, or its massively inferior AI, or its action button. I just want something that can browse the web, use some apps, take photos and make calls, and for the price of my iPhone, I can buy 3 Pixel 9a's, and so I did just that, I bought a Pixel 9a.