Performance with Docker is slightly worse, but it shouldn't be an issue for a long-running process. The main problem I've run into is that, by default, Docker logs will eventually fill the disk and crash the server. You have to change the logging system and then delete and recreate all of your containers, because there is no way to change the logging system for existing containers.
You have to login every 30 days which is pretty annoying when the game is at its core 20 years old and could be played entirely disconnected back then.
I really like the aim of this, but I have a hard time following the terminology, despite being someone who preaches and teaches functional core/imperative shell.
Have you had problems with Nix on macOS? Nix works great for me, but I can't use it much to deal with installs or synchronize dependencies because the devs on macOS can't get Nix running.
A few people I've worked with have used our nix build successfully on MacOS, but I stick to Linux. They've told me it works fine after making some dependencies conditional. I would've expected it to be death by a million papercuts, so I'm delighted it works and a VM isn't needed.
We have devs on MacOS with Nix working, but we do encounter inconsistencies. Production & other devs are on Linux and they are having a much better time.
I have no affiliation, but I tried the free trial of their tool and I found that the Code Health metrics made a lot of sense. The files that it pointed out are definitely overly complex and more difficult to change than they need to be. But it gave perfect scores to some files with complex jobs that are better-designed. I recommend just giving it a spin on a codebase that you are very familiar with.
In production, I may only want one connection pool to a DB, and in that case global state is pretty much equivalent to passing state as an argument. Development in a Clojure REPL is a different story. I have one connection pool for the dev server, and a separate pool to run tests against. The test db is re-created from a template between each test run, without affecting the dev db at all. I can trivially have multiple test pools if I want to run tests concurrently.
I also have a separate service that the server makes calls to, which doesn't run on this server in production (it has its own production server), but does run in dev and test. Each dev/test system runs a separate instance of this service, which has its own separate connection pool(s), and setting this up was trivial.
Needless to say, failures are reproducible and meaningful. There is no mocking -- we test against real local services with real local DBs. (There are still some remote service calls which I'm slowly replacing, and some flakey, unavoidable remote dependencies in a few browser tests).
I didn't do anything special to make this possible other than naming the config files "service-name-config" instead of just "config". It is just the natural result of passing state in explicit arguments. The same is not true of global state.
> It is just the natural result of passing state as explicit arguments.
But nothing you've mentioned here is intrinsic to mutable state. It seems like all that's happened is you identified a part of your program that you wanted to be configurable and exposed a configuration knob. If for example you wanted to make it so that there is a test mode that where you want to prefix "test-" to every string written to the DB that would also probably involve a new argument somewhere. There's nothing here special about the mutable state part of it.
I just added a test case that importing org.apache.logging.log4j.core.lookup.JndiLookup throws a ClassNotFoundException. Any log4j2 class would work too.
Clojure is strongly typed and dynamically typed, not "untyped". Much of its core behavior is built on interfaces like ISeq.
It's not uncommon to use a spec library like clojure.spec or malli, whose benefits overlap those of static typing. I'm not sure if there is a measured improvement from their use, but they have either advantages like facilitating generative testing that do help one to write more correct software.
The term "untyped" means anything that isn't statically typed (or just "typed"). This is because (static) types and dynamic "types" are two very different objects, and only the former is called "types" in programming language theory and formal languages in general.
I am well aware of clojure.spec, and it, as well as many other techniques employed in development, are probably among the reasons why types don't actually seem to have a big relative impact on correctness.