This is how RunUO/ServeUO was designed as well. I'm pretty sure it's a common design practice that came from muds. The server is only responsible for network and communications - everything is then orchestrated through plugins/scripts/systems that can be compiled and reloaded while the server is running. The more modern way is now to separate your "server" from your "modules" in distributed-systems architecture (just deploy the new module to the farm) but the idea still permeates.
Still, RunUO was C# and like java, recompiling and loading is trivial. Doing it in C/C++ land is heroic.
At least for java, you can spin out a new class loader which intercepts all class loading tasks and basically shim the dynamic module from the permanent runtime platform very trivially. For unloading, you'll likely need conventions on how to shut it down cleanly to not leave a ton of garbage around. Want a new version? Spin up a new class loader, rinse and repeat. If you need stateful between versions, you clearly need to mindfully build a facade for that outside of the dynamic parts. This is all not hard, but not introductory either. That said, I basically wrote similar DLL loaders in my first year writing c so many decades ago, so shrugs.
Class loading from a DLL is one thing, persisting the state and reloading that state on-top is another. In Java, you can use the class loader to load new instances of a class, but it's up to you to transfer state before giving that class over to your program again. In C# it's a similar story with System.Runtime.CompilerServices. The trivial bits are the fact that you can use reflection to do this rather easily in both Java and C#. In C++ isn't not as simple. The struct/data signature may change, you need a temporary storage vessel (old object?) to copy to the new class object. While it's still "digital plumbing", it's not that easy to do and then hot-reload the whole stack.
Still, RunUO was C# and like java, recompiling and loading is trivial. Doing it in C/C++ land is heroic.