Ismael Celis

Ismael Celis

@ismaelcelis.com

895 followers408 following1.5K posts

This week in Eventsourcing: I started exploring a kind of little algebra for declarative event queries. You can express arbitrary "states" made up of sequences of events with ordering assertions.

I don't think this is something I really need, but robots make it easy to play with things now. This DAG is generated off my web framework's configuration, which is defined as a declarative dependency tree in code.

Sunday hack: now that my Ruby Eventsourcing library has an API for statecharts (nested state machines), I reused it to model the lifecycle of consumer groups running in the background, including failures and retries.

Speaking of which: the Sourced dashboard will now render a very informative page derived from Ruby state machines. Executable specs!

Ismael Celis@ismaelcelis.com

Today in Ismael's Confirmation Bias ™️: video on how "executable specifications" are the way forward for coding. I've been trying to build this for a while now! www.youtube.com/watch?v=EkH_...

Guys if you think type systems are beneficial because they make invalid states impossible (they are), think of declarative state machines as a type system for your app’s behaviour. They make invalid paths through your system impossible.

And I’m not talking about those ORM-based state machines that only really capture a tiny sliver of the behaviour (the rest still scattered across different layers). The entire app.

Of course this doesn't mean that _all_ frameworks are suitable to the task.

Ismael Celis@ismaelcelis.com

There's a lot of talk about how abstractions and frameworks don't matter anymore.

IMO append-only logs are obviously a simpler way to reason about software, and they remove a whole class of problems, but CRUD-pilled devs find it really hard to grasp (and for me to explain). There’s something fundamentally different about the structural / causal views of software design.

It also took me a long time to fully internalise, but I never found CRUD simple to reason about, so I had an incentive to keep exploring. I wonder if it’s just a personality thing.

This came across as patronising but it’s the opposite: I’m a dummy with very poor detail retention, so I’ve always struggled with CRUD and still look for mental models that let me reason about the flow of information without the detail.

And here it is! Event-sourced, server-rendered, async, nested state machines in Ruby!

Ismael Celis@ismaelcelis.com

Good progress implementing state charts on top of my async event-sourcing runtime. This is going to be awesome.

The Finite State Machine, that fundamental model of computation, has causality built in. Our hyper-sophisticated CRUD frameworks don’t. Behaviour lives in the programmer’s head instead of the programming model. CRUD is a broken paradigm.

Setting this up for my side projects now. This is the boot dependency graph for my Sourced Ruby gem. This will probably make it into the admin dashboard and CLI. Fully inspectable programs.

Ismael Celis@ismaelcelis.com

Good progress of my configuration Ruby library. Libraries can declare typed components, host apps can implement them and declare their dependencies. It does dep. resolution and starts/stops components in the right order. And it can be inspected without running. Here, Mermaid for dep. graph, and tree

Good progress of my configuration Ruby library. Libraries can declare typed components, host apps can implement them and declare their dependencies. It does dep. resolution and starts/stops components in the right order. And it can be inspected without running. Here, Mermaid for dep. graph, and tree

View full thread

ENV values are transparently decoded via Plumb's form codec. You just declare the Ruby type of your component, and the library coerces it for you. Example: dates, Money, your own custom types.

Each "system" is just a tree of components all the way down. You can mount a system on a host system. Ex. to mount and configure libraries on an app.

Good progress of my configuration Ruby library. Libraries can declare typed components, host apps can implement them and declare their dependencies. It does dep. resolution and starts/stops components in the right order. And it can be inspected without running. Here, Mermaid for dep. graph, and tree

I also added helpers to load config from the ENV. It's built on top of the same underlying APIs, so other data providers are easy to add.

Something very common with English-speaking academics: impressive knowledge of the subject (here: conquest of the americas) while utter ignorance of the languages (basic Spanish pronunciation). It’s so jarring!

I don’t expect impeccable accent but at least be aware that the Spanish “J” never sounds as in “Jane”! Or how can you be an expert on a guy called Velasquez and not know that the U is silent. It just makes me doubt their expertise.

Or saying “Francisco Pizarro” (Castilian “frahn-THEES-koh”) as the Italian “Francesco” FFS (ノಠ益ಠ)ノ彡┻━┻

Use LLMs, but at the end of the day your boss or client will ask you (or their LLM!) how the app works, and expect a clear answer. Comprehensibility-has always been the bottleneck, and MVC/CRUD has never led to comprehensive systems at scale, LLMs or not.

It’s not about Ruby or Rust or whatever. It’s about having building blocks that describe the flow of information without drowning in ad-hoc glue code nor low level mechanics. Have that, and both your brain and your robot will be happy!

View full thread
View full thread

You can use it for simple config values, but also components that have a lifecycle, such as workers that run in threads or fibers (you can do this with Dry::Types too, I think)

This is heavily inspired by Dry::System, but with some features that I wanted for my apps: * An app declares the components it needs, separate from their implementation. * Component implementations can declare their dependencies statically (you can inspect the dependency tree without running)

I made good progress with an app generator for Sidereal, my framework for reactive, command-driven apps in Ruby. It generates an app with a default directory layout, a CLI, and an initial app that shows how you can send commands from anywhere. Reactive and multiplayer by default!

Causal reactivity: if a UI sends a command to the backend, it probably wants to update itself whenever the backend produces an event correlated to the initial command. Make this the default behaviour.

Give the robots abstractions to test your logic in Given, When, Then terms. Then both you and the robot have a common, high-level understanding of what the behaviour should be without noisy mechanics.