Back in 2002, the world was exciting for Windows desktop
application developers: Microsoft released the .NET Framework!
With it came the completely new language C# (do you remember it
was called Cool prior to the release?) as well as
various new subsystems. WinForms was the tool of choice, meant
to take over from previous efforts by Microsoft (C++/ActiveX/VB
based development, as well as MFC) as well as competing dev
platforms like Delphi. “Meant to take over”, according to some,
at least — as we know, time moved on and things became more
complex instead of simpler, and the success of individual
platform technologies harder to judge.
On the basis of the .NET Framework, the next important client
UI technology was of course WPF, and to this day both WinForms
and WPF remain relevant to all devs who create native UI apps
for Windows. In fact, these technologies can cross over to
platforms other than Windows, for instance by using
Wine
or
Avalonia UI.
Ever since 2001, somewhat ahead of the first public .NET
Framework release, DevExpress has produced high quality
components for desktop apps. We never stopped these
developments, and many of our customers maintain such
applications today and want to continue doing so. If you are
one of them, this post is for you. If you are curious for any
other reason, this post is of course for you as well!
The New Complexity
In the last twenty years, many things have changed about the
way software is commonly developed. This is not a bad thing, it
means that we learned from mistakes that were made and gained a
better understanding of best practices that help us avoid
issues we recognized. On the other hand, it takes time and
effort to follow such developments and changes, and the
practical problem of moving from point A (an existing
implementation) to point B (the new and hopefully better plan)
is never easy to solve.
In this article, I will begin by providing an overview of
technical areas where important changes have happened over the
years, and of the support DevExpress offers for the required
steps that move your apps into the future. I intend to follow
this up in the near future with detailed posts to cover each
specific topic.
For this overview, here are the main points:
-
The separation of backend and frontend in application systems
has changed. Where simple structures like client/server used
to be the norm, distributed systems of varying complexity are
typical today.
-
For data persistence, the number of commonly used options has
increased. It now includes NoSQL solutions as well as
distributed structures such as Event Sourcing backends.
-
UI apps need to take the new architectural concerns into
account and work asynchronously, which creates technical
challenges for developers.
A Changed Understanding Of Backend And Frontend In Desktop Apps
There was a time when desktop apps did everything themselves:
UI, (business?) logic, data storage. These kinds of apps
certainly still exist today, though they are usually utility
applications rather than full-blown business applications. A
new wave of such apps has become ubiquitous on mobile devices,
although even those tend to integrate cloud storage and backup
mechanisms.
For a typical business application with a desktop focus, a
two-tier “client/server” type architecture was the most common
structure for a long time.
Some small changes to this architecture arrived a long time
ago. For instance, we learned that it wasn’t safe to expose SQL
database systems to the internet directly, and this introduced
the requirement of either a VPN connection or a safe proxy
service. From a logical standpoint however, simple
client/server architectures seemed sufficient, and with
concepts like the integration of a .NET CLR into SQL Server
this approach enjoy lots of support for years.
Things began to change slowly when it became apparent that
applications often needed support on the side of the data
storage server, in order to implement certain functionality
efficiently. Some business logic algorithms can be expensive to
run on a client that uses a potentially slow connection to the
database server — if millions of roundtrips are necessary, you
can either spend lots of time optimizing the algorithm or
choose the easy and efficient way of implementing the logic
near the data. This idea often resulted in architectures where
select services ran in the data server environment, shortening
access times, while the direct interaction of the desktop app
with the data server remained the same for most other purposes.
Clearly one problem with this type of structure is that various
different components need to work together in complicated ways.
One important aspect is security: if Alice is logged into the
desktop app, how do we ensure that Alice’s data access on the
storage side is limited according to business rules, and that
all separate services, when they act on Alice’s behalf, are
subjected to the same restrictions? Maintenance also became a
concern, since various endpoints had to stay in sync with
aspects such as ORM based “clever” business objects and event
based logic implementations on that level.
A typical “middle tier” architecture was the next evolutionary
step to combine all data access and related logic in one place.
This brings us forward to the final step, at least for the time
being, which was again based on an obvious maintenance issue
with the monolithic middle tier. Functionality was separated
into individual services which could be maintained more easily,
while the logical structure was kept the same, without direct
access to data storage for the frontend app(s). It is important
to keep in mind that implementation aspects also moved on in
the same timeframe, for instance resulting in loose coupling
more often than not, perhaps using REST or gRPC interfaces,
which in turn makes such structures very flexible and very
open, or at least either open or flexible depending on
requirements.
This summary certainly misses a few aspects and details of what
happened over many years, but it shall suffice to provide some
background. For developers who focus on desktop apps, here are
the most important changes to consider:
-
Business logic is not primarily implemented in the desktop
app itself, but largely in services elsewhere. This may mean
that such logic is not our responsibility, but more likely we
need to interact with it, possibly augment it (for instance
for validation that usually splits, shares and duplicates
responsibilities between services and frontend apps).
-
Data access requires defined interfaces. It is possible to
integrate fully dynamic query scenarios, for devs or even for
end users, but since this requires special solutions it
should be done only if requested as a business use case.
Backend-for-frontend (BFF) endpoints are more efficient at
runtime, and they make data access both safer and faster!
-
Authentication and authorization of users should happen in
cooperation with services. In reality this is easy since it
only needs some interaction with a token service on login and
logout, and the passing of tokens with all API calls to
services.
-
To make applications future safe, it is a good practice to
separate UI from logic. Patterns exist to support this
effort, with MVVM the most common one today. Applying this
pattern means that your frontend app can interact with any
middle tier or service based backend today, and move on
towards whatever changes tomorrow will bring.
In a follow-up post, I will describe in some detail how
DevExpress tooling and library functionality helps with the
implementation of MVVM patterns for WinForms and WPF, and with
the generation of forms required for security system
integration.
Data Access
In the past, many desktop applications — and others, such as
ASP.NET web apps! — were written with a SQL backend in mind.
This approach reflected the basic client/server architecture
described above, although solutions with automated
synchronization of multiple SQL Server instances, or similar
approaches using other RDBMS, were widely used for complex
deployment scenarios. Persisting data in relational systems
required the usual exercises in normalization, and for many
years now ORM products were commonly used in object oriented
environments such as .NET (but also Java) to save developers
some work.
One development of recent years has been that NoSQL database
systems have become more common in all types of software
systems. There are many reasons for this, and of course SQL is
still available. But the enhanced flexibility of many NoSQL
systems, greater scalability, higher availability, and the ease
of use from object oriented environments are strong arguments
that favor, for instance, document databases. Since many
software solutions have backends that run entirely in cloud
environments, a good choice of such systems is easily
available, and solutions like
Microsoft’s Azure Cosmos DB
even provide multiple APIs in one cloud product.
Document databases are very useful since they allow OO devs to
drop objects into databases in a more “natural” structure than
that required by RDMBS, and they resolve all the common issues
around data “versioning” in normalized structures at the same
time.
In some environments, frontend applications do not interact
with data storage directly at all, and they may be completely
unaware of the structures used internally. The pattern CQRS is
important since it promotes the understanding that there are
two separate channels of information flow: the “write side”
receives commands with data, for instance new or changed
objects, from frontend apps and stores it somewhere. The “read
side” receives queries and responds by returning data. The data
“models” that are sent and received by frontend apps are
process-specific and don’t necessarily represent either objects
used in the frontend app, or persistent structures used by the
backend.
Event Sourcing is a separate pattern that is often used in
conjunction with CQRS, and its main effect on the architectural
structure is that data services tend to be even more granular,
data models more purpose-specific. Typically, “backend for
frontend” (BFF) data models are used in this context, often in
Microservices deployments, which makes for great
maintainability and fantastic performance.
DevExpress data aware components on all UI platforms support
data binding through interface layers. .NET has its own such
layers, of course, whether you consider
BindingList<T>
(commonly used in WinForms) or
ObservableCollection<T>
(more typical for WPF), or the interface based architecture of
LINQ with its Expression Trees and
IQueryable<T>
. Since many DevExpress data-bound controls have specific
runtime querying requirements, they support their own
translation systems from user-space interactive UI features to
server-side data queries using LINQ or any custom
implementation.
To make the component data binding features easier to use,
wizards help you create bindings for many ORM and API based
scenarios. Additionally, the
Backend Web API Service
or
XAF Middle Tier Security
provides a ready-made service application project for data
access and security, accessible from any .NET application as
well as web or Blazor projects.
As above, these are the most important changes to keep in mind
for desktop app developers:
-
A shift towards object-based data persistence may remove the
need to model data in a desktop application. If you are
responsible for all parts of your application system, you may
need to implement such models elsewhere.
-
The same shift can also mean that relational concerns, and/or
ORM, are not part of the picture anymore.
-
In teams where complex backend architectures are built, this
work will usually be separate from frontend development. If
you specialize in the frontend work, you may be presented
with API requirements which have no recognizable data
persistence aspects.
-
You can expect DevExpress UI components to supply APIs that
allow you to interface with application backends — in some
cases this may be fully automatic, in other cases you will
need to build adapters.
I will publish a follow-up post that details the technical
features in DevExpress components to support binding to any
backend data architecture.
Asynchronous Frontend Apps
Technically, even client/server apps are asynchronous — or at
least they can be. The same is true for apps that work locally
and store data only in the file system. The question is, what
does the app do while it’s waiting for data? It can either
block execution, so that the user can’t do anything else —
displaying a modal status bar is the same thing from a user’s
perspective! — or it can remain “live”, allowing the user to
keep working, or to potentially cancel a running operation.
The big difference is that with filesystem operations the
potential wait time is often measured in milliseconds or less.
With database operations it depends where the server is, how
many clients it serves, whether it’s well optimized for the
query at hand, ... it may return quickly or it may not, and
there is an unfortunate number of apps out there which simply
make the user wait until there’s a response, as if devs can’t
do any better!
Yes, we can do better, and this becomes more and more important
the more distributed the systems are with which a frontend app
communicates. We can’t rely on ... anything, really. When a
query is sent, it can receive its response immediately — but it
may also take a while, or it may never return, and we need to
deal with that. This sounds like a challenge, and it is, but
it’s also an opportunity to deliver apps that are more
responsive for the user, allow work on multiple jobs at the
same time, are open to users to define their own workflows.
The technical side may be quite simple, as long as we
understand C# features including
async
and
await
, and the use of
Task<T>
. There are of course other aspects to multi-threading in UI
apps:
here’s a starting point for WinForms
and
a similar one for WPF. On top of all that, it may be important to consider the
architecture of the overall application system — it could be
one where calls are made through HTTP(S) and receive responses
in the near future, but it may also be message based so that
queries and responses are completely decoupled.
Needless to say, DevExpress UI controls can work with any
scenario summarized above. A further future post will include
detailed samples to illustrate how you can create modern
desktop apps that take full advantage of distributed
architectures and deployments of modern software.
Your Feedback Matters!
Free DevExpress Products - Get Your Copy Today
The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the
DevExpress Support Center at your convenience. We'll be happy to follow-up.