Clean Architecture with Xamarin Forms

Clean Architecture with Xamarin Forms
Photo by Lance Anderson on Unsplash

Delivering quality software is a big deal these days.

The quality, good or bad - of a software solution has significant impacts on the developers who make it, the companies employing them, and, most importantly, the users who rely on it to provide some value in their daily lives — quality matters.

How is Software Quality Measured?

There are all sorts of things like test coverage metrics, code analysis tools, performance benchmarks, SLAs, and on and on that can be used to quantify a software product's quality.

The value in each of those is debatable and varies wildly when applied to the unique requirements, technologies, methodologies, and environments that must be combined to produce software. Each team and project are different, with their own DNA, making them hard to compare.

There are, however, a few practical attributes we can use when discussing quality, and they apply to just about any application.

Deployment - How quickly can the software be built and deployed? How much friction or manual intervention is involved?

Testing - Can you test important logic quickly and easily using a suite of automated unit tests? Is unit testing constrained by databases, network connections, or user interfaces required to make the code run?

Maintainability - As requirements are added or changed over time, can developers refactor or extend the code with the confidence they haven't broken working functionality while trying to implement something new?

Get notified on new posts

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.

Better Software With Clean Architecture

With the belief that good software starts with good architecture, Uncle Bob introduced the programming world to The Clean Architecture way back in 2012 followed by a book a few years later.

The idea behind Clean Architecture wasn't anything radically new when first introduced. It is based on existing architecture patterns like Hexagonal Architecture (a.k.a Ports and Adapters and The Onion Architecture.

Some key characteristics and benefits of The Clean Architecture are:

No Dependency on Frameworks

The architecture does not depend on the existence of any specific software library or technology. That means using frameworks as tools instead of making your code work within their opinions and constraints.

Testability

We can test the business rules without a UI, database, web server, or any other external dependency.

UI Agnostic

We can easily change the UI without changing the rest of the code. We can hook up the business rules to a modern SPA application in the browser or a mobile app, as we'll see in this guide. It doesn't matter; we can replace the interface without impacting the business rules.

Database Agnostic

Do you use a relational database like Sql Server or MySQL? Maybe you're using document-based storage with Mongo or CouchDB - or even a combination of these. Similar to the UI, it doesn't matter because, in Clean Architecture, there is no binding between the business rules and database.

Independent of Any External Dependency

The business rules don't know anything at all about the outside world.

The [Golden] Dependency Rule

The key to all of this is The Dependency Rule.

This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.

Uncle Bob

The diagram shows how the different regions of an application fit together within the architecture. Note, the arrows moving from the outer layers inward toward the Use Cases and Entities.

The outermost layer represents the web frameworks, database drivers, and other details required to run our application. We typically write very little code in here.

The Interface Adapters layer is where data is converted or adapted into a format convenient for the Use Cases and Entities and vice versa. More concretely, things like controllers, view models, presentation, and UI code live here.

The Use Case layer contains your application's business logic. The code here is pure and knows nothing about the outer layers and their dependencies (i.e., databases, UI, etc.). These operations typically exercise additional business rules defined within the Entities to satisfy the use case requirements. This code is unit test-friendly. Any reference to external facilities like a database or HTTP client for talking to a remote API gets abstracted behind an interface, making them easy to mock within your test.

Entities reflect the objects with behavior and data that many different applications across the enterprise could use. If you don't have an enterprise, that's ok - these could be business objects that contain the operations and data relevant to your specific application. Similarly, in domain-driven design these would be the entities that model the domain. Regardless of how you classify them, the Entities are similar to the Use Cases in that they too are kept pure, contain only the code to enforce the high-level policies and business rules while remaining decoupled from the clutter and baggage of the dependencies in the outer layers.

Control Flow and Boundaries

The lower right corner of the diagram above shows how the controllers and presenters interact and exchange data with the use cases in the layer beneath them. We'll see how this works below by applying the dependency inversion principle to keep the high-level code in our use cases loosely coupled from the UI's low-level details.

Xamarin.Forms and Clean Architecture

Ok, enough theory - let's get into some code and look at how we can apply the principles of Clean Architecture in a real-world(ish) mobile app built with Xamarin.Forms. A mobile app makes a good demonstration for these concepts as even the simplest ones on the surface can be highly complex under the covers. Prior knowledge of Xamarin.Forms isn't required to follow along, but if it's new and you're interested in learning more, here's a good place to start in the official docs. At a minimum, Xamarin.Forms provides a way to quickly build native apps for iOS, Android, Windows, and macOS, completely in C#.

Virtually every app requires connectivity to fetch data from a backend resource over the internet. Most apps require some form of local database resident on the user's device. Many apps use available hardware-based features like cameras, motion, position, environmental sensors, and more.

Equipping our app with the basic level of support for these features can often involve 3rd party libraries or dependencies and, at a minimum, some amount (seldom trivial) of not very interesting infrastructure code. This can begin to add up quickly depending on what we're trying to do.

If we're not careful, this code can easily get tangled up with more important business logic, potentially leaving us with large regions of the codebase that are buggy, hard to test, and tricky to change.

Let's see how The Clean Architecture can help us avoid some of these pitfalls by allowing us to write cleaner code that is reasonably well-factored, expressive, and relatively easy to test and maintain.

Demo Project

Our app "Open Standup" provides a simple function for GitHub users to post about the projects and repositories they're working on. It also offers a few basic social features such as comments and likes.

The GitHub repository for this project is here and Android users can download the app from the google play store (requires GitHub account).

Solution Structure

Let's start by identifying each of the projects in our solution, along with a brief description of their purpose.

Keeping our projects focused, properly encapsulated, and decoupled from one another is always a good practice. It will help organize our code into the appropriate regions as requested by the architecture.

Implementing a Use Case

The gif above demonstrates the primary use case of our app: submitting a post. Let's walk through the design and implementation of this functionality within The Clean Architecture.

Our app's UI is structured using MVVM so the code that defines the layout, controls and styling lives in the views and is separate from the code containing the methods and commands that call into the business logic which lives mainly inside the view models.

I briefly mentioned above about control flow and how the code in the outer (UI) layers interacts with the business logic in the use case layer. Now we'll see how that's implemented.

For our application's Publish Post feature, the chain of processing begins in the EditPostPage where the post button's clicked event handler triggers the view model's PublishPost method seen below.

As we can see, there's not a lot going on here. _indicatorPageService is used to show/hide a modal spinner while the app is busy. The only interesting line is the middle one where we're using the very awesome MediatR library to dispatch a call into the publish post use case.

MediatR is a very handy library that provides a simple implementation of the mediator pattern in .NET. This pattern controls how objects interact with one another while keeping them loosely coupled. In the case of MediatR, it accomplishes all this with a relatively simple API we can use within our code to send and handle in-process messages between classes.

The view model's _mediator reference, as the name would suggest, represents the mediator object which provides a request/response messaging pattern our classes can hook into. MediatR supports two simple messaging models: the request/response mode we'll use, which dispatches messages to a single handler. And notification messages that are dispatched to multiple handlers.

The PublishPostRequest message dispatched by the view model is handled by the PublishPostUseCase, which lives in the Core project.

These two classes demonstrate MediatR's request/response pattern and its simple convention requiring the request class to implement IRequest and the handling class IRequestHandler. From there, MediatR can perform its magic by allowing these two classes to communicate in a completely decoupled fashion ✨.

Note that the view model has no direct dependency on the use case class. It uses MediatR's IMediator interface, which takes care of routing the message to the class responsible for handling it.

The use case class contains a single method: Handle(PublishPostRequest request, CancellationToken cancellationToken) where all of the logic to publish a post lives.

All of the dependencies required by the use case get injected by the Autofac container. Their interfaces are defined within the Core project/layer, but their implementations are defined elsewhere in the outer UI and infrastructure layers. This is the dependency rule in action. The business logic in our use case references only abstractions of these services and knows nothing about their low-level details or additional dependencies they use. This helps keep the use case code clean and easily testable.

The one dependency here that might look a little mysterious is _outputPort of type IOutputPort<PublishPostResponse>.

The IOutputPort represents the abstraction that allows the UI's presenter classes in the outer layer to communicate with the use cases. The PublishPostPresenter in this case is implemented within the OpenStandup.Mobile project along with the rest of the app's UI code. The output port's Handle() method expects a single parameter that is a response object from the use case used to pass any data between the use case and presenter.

The interface is used here because a direct call and reference between the use case and presenter would violate The Dependency Rule. The use case calls on the IOutputPort interface in the inner circle, which the presenter class implements in the outer (UI) layer. This is an example of the dependency inversion principle we can employ to keep classes loosely coupled.

The presenter's handle operation resets some properties on the view model and pops the navigation stack's view if everything worked (the API call succeeded) or displays a dialog with an error sent from the use case if it failed.

Flow of Control

To summarize the chain of processing here, it starts in the view model, moves through the use case, and then winds up executing in the presenter.

Wrapping Up

This article explored how Clean Architecture can help us build a well-structured and robust application that places the important domain-related code at the front and center of the application's design.

This approach yields some great benefits:

  • Expressive and consistently structured domain logic that is easier to understand and troubleshoot.
  • Business logic isn't bound to any particular flavor of database, external API, or presentation format.
  • Improved testability as core use case logic is free of any dependencies or frameworks, which can be tricky to set up in tests.

On the flip side, this approach and the abstractions and boilerplate it asks for can be overkill for simpler apps. And for developers or teams new to this architecture, there will be a barrier to entry to overcome as they adjust their thinking and approach to conform to its demands.

Source code here

Demo app on google play (requires GitHub account)

Get notified on new posts

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.

Related Posts


Better Software Design with Clean Architecture
Better Software Design with Clean Architecture

Jun 30, 2017

Read more
Building ASP.NET Core Web APIs with Clean Architecture
Building ASP.NET Core Web APIs with Clean Architecture

Sep 30, 2018

Read more
Get notified on new posts
X

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.