Thank you for joining us for today’s 5 Minute Tech Challenge! We’re so glad you’re a part our community. Today, we learn from Aditya Gune, a tech writer and software engineer at Jetty. Take it away, Aditya!
Most SaaS product engineers spend their careers in the web ecosystem, working with one of a handful of frameworks to build cloud-based applications. For those of us in the web ecosystem, desktop applications might seem remote at best, obsolete at worst. I recently built a Windows desktop app for document scanning and management for a local company, and I found this maiden voyage into the desktop world enlightening – not least because I found myself engaging with the UX more closely than I ever had in the web app world. I learned quite a lot from the experience, but here are the three most important takeaways I came away with.
Know your framework
This seems like an obvious point but is even more important for desktop apps than it is for web apps. Most web applications work roughly the same way – an ORM reads and writes from a database cluster, an application framework routes HTTP requests and contains the business logic, and a client-side frontend governs the UI. Despite variety in languages and build systems, web application design patterns stay fairly consistent, and most SaaS products will not find their functionality limited by their choice of framework until the product is large and complicated.
Desktop app frameworks, on the other hand, are pretty diverse in design patterns and quirks. Some frameworks, such as Electron, significantly abstract away lower-level concerns such as threading and device access. Knowing the limitations of your framework and the assumptions baked into it is critical to successfully building the app you want.
For this application, I prototyped both JavaFX and Microsoft’s Windows Presentation Foundation (WPF). My application required low-level libraries related to hardware device detection and image acquisition, and I found that WPF is far more intuitive in this regard than JavaFX. Because WPF is a Microsoft framework, it allows easy access to libraries compliant with the Component Object Model standard (a system to allow inter-process communication between Windows processes, similar to RPC standards), and it takes less work to integrate them into the .NET build than it did for JavaFX’s Maven build system.
Identifying available UI libraries for your framework is also a good idea. JavaFX comes with a free tool called SceneBuilder to make UI development easier, but has a relatively fragmented ecosystem for controls and UI elements. Microsoft’s WPF uses the stock Windows UI controls, but has a rich open-source ecosystem. There’s even a package that extends Google’s Material Design pattern for WPF, allowing for plenty of flexibility in your UI.
There is no such thing as a bad framework, but identifying the best one for your specific use case is an important first step to building a successful desktop app. For my application, I identified that WPF most closely matched my requirements and had the most thorough documentation. (As a side note, it’s interesting to see how far the Microsoft stack has come. It was considered archaic in the mid-2010s when Javascript was taking over software development, but the developer experience has improved significantly since then.)
Embrace multithreading
A common feature of desktop apps is that asynchronous work has to be specifically performed on a separate thread to avoid blocking the UI. This problem presents differently for the web ecosystem, especially in the client-server model. Most asynchronous work in a web app architecture is handed over to another thread, or even something like a message queue on another set of infrastructure.
Desktop applications don’t have that luxury – the same framework has to handle both UI presentation and asynchronous business logic, and there is no infrastructure to offload long-running tasks unless your app is part of a larger architecture. Understanding how your framework handles background tasks becomes important in this context. WPF and JavaFX both allow async work to be specifically delegated to non-UI threads. WPF also has a robust system for event handling, providing another way for services within a desktop app to communicate with the UI asynchronously.
It’s also important to understand how resource-hungry your application’s data processing workloads are. Unlike client-server web applications where the UI runs off a completely separate set of resources, a desktop app has to devote the same set of resources to both the UI and the business logic, and also has to share those resources with other desktop apps. If your desktop app is a client in a client-server architecture, this will be much easier as you can move that resource-hungry work to the server. On the other hand, if your desktop app has to stand alone, identifying the resource commitment required can make a huge difference in your end-users’ experience.
Where do you keep your data?
Most apps need some kind of local data store, whether it’s a local database or a filesystem. Even if your desktop app is connected to a database on a server, chances are you’ll need to cache data somewhere locally. Both the local database and the filesystem have tradeoffs, and it’s important to consider them when designing your desktop application.
Storing data to the filesystem is the easiest option regardless of framework. Almost all desktop app frameworks have some kind of interface for accessing local files, and the code required to set that up is very low-effort. It does come at the cost of scalability, searchability, and security though. Whatever data you store on your filesystem is unsearchable and inaccessible unless it’s all pulled into memory. Depending on the volume of data, this may be impossible. My application stored searchable metadata about the scanned documents, but loading hundreds of documents into memory to search and filter them was unrealistic.
Security is another important concern. Any desktop app that caches sensitive data to local files opens the door to the possibility of those files being copied by a user that has access to that machine. Encrypting stored files is one solution, but that presents its own set of challenges – where do you store the encryption key? How do you derive that key?
Since my application’s requirements included being able to scan documents containing PII, the database was the more secure option. WPF supports multiple database engines and integrates easily with the Entity Framework ORM.
The major downside to this option is that you now have to build and deploy a whole second component to your app. WPF doesn’t natively package its applications into installers (the .msi files with the EULA screen from days gone by). A natively built WPF app just turns into an EXE file. An app with a local database needs a separate build and distribution system that allows you to include the local database with the EXE file.
Closing thoughts
Most of the patterns in software development are the same regardless of whether it’s web-based or desktop-based, but there are important distinctions in the details. The most noticeable change was that I found myself setting aside my technical knowledge to think like a human using a computer while developing this app, simply because the user experience was closer at hand than it normally is for a large enterprise SaaS ecosystem.
Design choices around UI appearance, resource management, security, data storage, and even naming all change when your infrastructure narrows to a four-year-old HP laptop. Understanding your framework and every technical decision made becomes much more important because everything – UI, logic, data store – has to be bundled into one discrete package that can be easily deployed, not to a server, but to a computer used by humans to help other humans.
Coming up in two weeks, we will be learning from Abhinav Ramakrishnan, a staff software engineer at Meta who likes building to solve real problems — the hairier the better. Abhinav will be sharing his learnings on Resiliency Through Failure. I may finally be vindicated for failing that math test in 9th grade.