Ask HN: What are modern architecture patterns for desktop applications?

49 points by brushyolaf 2 years ago

I built a web based prototype for an application using a Docker microservices architecture: postgreSQL, redis, traefik, etc. - the regular stuff - which works great. Now, the application's usecase is actually better suited in a offline environment with a standalone executable, and I'm struggling to make this transition.

(How does the app work? Think of the application as a file indexing system: you specify a folder on your local hard drive and the program will analyze the files (might take up to 30 minutes) and store metadata for it in a search-optimized datastructure.)

From what I understand most applications also separate frontend and backend layers and let the processes communicate via message passing over some form of channel and protocol. The solutions I found include Unix Sockets with gRPC (https://docs.microsoft.com/en-us/aspnet/core/grpc/interprocess?view=aspnetcore-6.0), local HTTP Servers with a REST API or gRPC, even JSON over stdin/stdout (https://github.com/xi-editor/xi-editor). Solutions for datastorage include custom file formats, sqlite databases or bundled mongodb instances into the main application.

I figured out the backend part with analysis running multithreaded and writing into a sqlite database. For the IPC I spawn a gRPC Server which clients connect to over HTTP/2. That way I compile a platform agnostic backend and platform specific frontends. It all works okish but I feel like it's unnecessarily complicated and there could be a common strategy I am missing.

*TL;DR: What are modern architectures for desktop applications equivalent to a microservice architecture with backend/frontend separation, message queueing, pub/sub, authentication, ...?*

pavlov 2 years ago

What's your UI framework? This question is difficult to answer without knowing that.

In general it sounds like you're working too hard to introduce a strict process separation between the UI and the backend. A desktop application won't have multiple clients connecting to the backend, so you can run everything in one process. That's the use case for which SQLite is designed.

Your UI framework may introduce some level of process separation anyway: if the UI is a WebView or Electron, then it's going to run the renderer in its own process. So there's no need to go crazy with splitting up into tiny little services.

ahartmetz 2 years ago

OP, it seems like you are overdesigning. IPC takes way more effort than direct function calls - it's effort that is better spent on something actually useful. Let the requirements pick design patterns, don't try to cram in as many design patterns as possible.

Event handlers, model/view, independent-ish subsystems, frontend/backend are some typical patterns in desktop applications. Some of it depends on your framework - much of the architecture will be defined by the framework. The default should be doing things in the canonical way for the framework.

  • devandanger 2 years ago

    I agree if you have technical requirements needing it to support (x transactions)/second coming in, optimize your ingress architecture. If these transactions/messages are based on UI interaction in a single offline application, I can't imagine this being too complicated to begin with.

gte525u 2 years ago

It's not uncommon to split front end and back end logic into modules. I've seen lots of projects where the business logic is even shared between a suite of applications.

But it's very unusual to split it over an RPC/IPC boundary - it's usually just linked in.

Xi had specific design goals of allowing multiple independently developed front ends. There are use-cases for this type of development but it's largely not worth the effort (YAGNI).

The typical application architecture is going to depend on your use-cases. I.e a db app is going structured different from a game or a CAD program.

jenkstom 2 years ago

As an old timer it's pretty hard to take this post seriously. I honestly can't tell if it's a troll or not. I'm not trying to offend, more to point out that there are some big generational gaps in software development.

  • digisign 2 years ago

    It's true, but not entirely different than someone saying they've only built DOS apps and now want to build a Mac application and having quite a few misconceptions along the way. At least how I read it.

adinisom 2 years ago

If it were me, I'd try for a single process with the UI and the indexer running in separate threads.

Communication between threads could be as simple as global variables + a mutex lock.

Two problems to solve:

- Communication from UI thread to backend thread

- Communication from backend thread to UI thread

For communicating to the backend thread, I like using event flags but other people like using mailboxes. Both work. With event flags, you set a bit (for instance, backend_thread.start = True) and if the backend thread is sleeping, it gets woken up by the OS scheduler.

UI frameworks often provide a function to run your code in the UI thread, so your indexer could use that to periodically update the UI. For example you could have an Update() function that reads the global variables defining the state of the indexer and updates every UI element to match, and then the indexer periodically schedules Update to run on the main thread when it's doing its work.

In my work there's typically a state machine that coordinates UI + backend. It might start with state = BACKEND_STOPPED. Then when you press the start button, transitions to state = BACKEND_RUNNING and notifies the backend thread using an event flag. When the backend thread finishes, it can transition back to BACKEND_STOPPED and schedule an update to the UI.

----

Another pattern I've seen is using the database for communication. You might have a "jobs" table in sqlite with indexing jobs to perform. The front-end adds "jobs" to the table and notifies the backend thread to wake up. The back-end processes jobs and updates their status and notifies the UI thread when the status changes.

digisign 2 years ago

Not at all a fan of apps that run a webserver locally and interface through a browser. Will make a rare exception, but I wouldn't use one long term. Not exactly what you are asking, but similar.

I do like the mature pattern of having a number of CLI programs that can be run, and an additional GUI that can run them as well, for non-expert or infrequent users. Have a log that shows what commands are called by the GUI. This gives powerful automation potential for free. Look at Handbrake GUI wrappers for technical inspiration, if not UX design.

Then you can do whatever IPC you like, can be as simple as reading from stdin/out or maybe Unix sockets before resorting to TCP/IP. If work has to be done outside of the user session, write a service/systemd module definition with one of the CLIs.

Re: Data, as others mentioned sqlite can stretch a lot farther than was commonly believed until a short while ago. Try before resorting to servers.

dig1 2 years ago

On Linux, you want to check D-Bus [1] if you need IPC (but that is primarily used for message exchange between different desktop apps). Everything else you mentioned sounds like a recipe for bloat. Desktop GUI libraries (like Qt or Gtk) ship everything you'll need and you can get a platform-agnostic app without much of a problem.

> running multithreaded and writing into a sqlite database.

sqlite doesn't support multithreaded writes, so you'll have to use a queue or something like that. Qt/Gtk ships with that as well.

[1] https://www.freedesktop.org/wiki/Software/dbus/

  • flicken 2 years ago

    SQLite does support multiple writers, they take turns:

    > SQLite only supports one writer at a time per database file. But in most cases, a write transaction only takes milliseconds and so multiple writers can simply take turns. SQLite will handle more write concurrency than many people suspect. Nevertheless, client/server database systems, because they have a long-running server process at hand to coordinate access, can usually handle far more write concurrency than SQLite ever will.

    Source: https://www.sqlite.org/whentouse.html#:~:text=multiple%20wri...

anotherhue 2 years ago

Sounds like you're bringing network tech in, which is unnecessary and costly.

I suggest you considers structuring the reusable pieces as a library and then reaping the benefits of strong typing and absurdly low latency when building the non reusable UI.

Akka might be interesting if you were a team but you really can just use regular old method calls.

alkonaut 2 years ago

Make everything in-process otherwise you sold the main benefit of desktop.

The architecture for the “backend” isn’t different from a web or other app, the difference being that you don’t need to expose a api/rpc surface of any kind that isn’t regular methods.

Set up some good interfaces and/or view models. This will depend heavily on which front end. An imgui app will have very different preferences from a databound ui and so on. Very hard to make recommendations about this part without knowing what frameworks will be used.

But don’t pretend a desktop app is a web app.

eatonphil 2 years ago

I use Electron for DataStation [0]. Electron has builtin message passing between the UI layer and the main process. I built a small wrapper around this in TypeScript so I could have the messages be typechecked.

Within the main process I actually have it call a Go process to handle all the heavy lifting that goes on in DataStation like database queries, HTTP requests, etc. Communication between the Nodejs main process and the Go process just happens in temp files. All long-term data is stored in SQLite.

Happy to talk more about it if you want! My email is in my profile.

[0] https://github.com/multiprocessio/datastation

richardwhiuk 2 years ago

Why do you need RPC at all? You've got a single client.

pjmlp 2 years ago

Use the language features for modules and libraries, make the view models UI agnostic, and the owners of the data, use the view models on unit tests, it is as modern as always.

occz 2 years ago

I'd look to mobile applications for inspiration, as they have many of the same constraints as desktop applications do.

In the mobile space, MVVM is pretty popular.

  • ducharmdev 2 years ago

    Having started in modern frontend js, and now doing mostly .Net 6 API dev with a Xamarin client, I don't quite understand the obsession with MVVM. MVU seems clearer and more intuitive by far, but that could just be my web background talking.

    Edit: to give an example of what I'm talking about, see this article's visualization of the unidirectional data flow you get with MVU. This feature in particular makes MVU much less ambiguous than MVVM (IMO at least)

    https://thomasbandt.com/model-view-update

b20000 2 years ago

you can leave your web stuff at the door. unless you have a really good reason to use client/server architecture, or make everything async, just write your monolithic desktop app using a framework like QT which will interface with a database out of the box. or you can interface with sqlite directly. if you only need windows or mac support you can use platform specific languages and UI frameworks.

sorry_outta_gas 2 years ago

Your only supposed to add stuff when it solves a problem

jcelerier 2 years ago

> From what I understand most applications also separate frontend and backend layers

definitely not on the desktop, no

  • sneusse 2 years ago

    Well, you do but usually within the same process.

    I usually don’t because DearImgui exists and I usually do simulation stuff which benefits from the immediate mode approach.

ozzythecat 2 years ago

I worked on an embedded system in the logistics industry where one process drove rendering on screen, and two other processes did a lot of the backend work and implemented lots of business logic.

They communicated over IPC.

This was design this way on purpose, because the back end processes were existing applications. Rather than re writing them entirely, the developers added IPC communication.

Also, they believed it was better for a separate process to crash and just restart rather than showing it to the user and confusing them (the person delivering products to a grocery store).

The whole thing was a convoluted mess.

My advice to you is start simple. You don’t need multiple processes unless there’s a damn good reason. Everything should be a single process.

ki_ 2 years ago

i dont see why u need separation of backend/frontend... anyhow. i guesse u can make a frontend executable. On boot it checks if the backend is running or not. if not. run it. Then establish a form of communication. usually sockets. if communication is needed 1 direction, just keep a read() open on the backend, if communication is needed 2 directions, u will have to keep a read() open on both ends.

But again, separating front & backend for a desktop application, is kinda weird.

sneusse 2 years ago

So the user is waiting 30min and watches the progress bar?

What’s your frontend used for? If the progress bar is everything then maybe you should just build a command line app.