For those who just want code: https://github.com/SuavePirate/Xamarin.Onion
A strong and scale-able architecture is important in applications, especially in Mobile Apps. APIs and SDKs are constantly changing, new technology is constantly released, and team sizes are always changing. A solid Onion Architecture can save a development team a lot of time by making it simple to change service implementations, restrict access to certain areas, making logic flow easy to follow, and making testing isolated blocks of code easier.
Some of the important topics this will cover:
- Separation of Concerns
- Inversion of Control
- Dependency Injection
- Why all these things are important
This first post will talk about the general project structure and high level role of each layer in the solution. Later posts will touch on the individual projects’ code, why things are where they are, using the structure to build out tests, and ways to bend or change the structure to work for you.
For this example we will be talking about Xamarin.Forms, but the same patterns can be applied in the exact same way without it.
Lets just take a look at the high level structure and layers of the solution:
This is the lowest level. Projects within Domain should not reference any projects outside of this layer and should also avoid referencing any external libraries.
These are our base data models. This project shouldn’t reference any other projects, even within Domain.
These are the definitions for our data access layer. Repositories, Stores, etc. There should be no implementation in this project and should only reference the Model project.
This layer is what defines our Services and Business logic without implementing any of it. Projects in this layer can only reference Model layers of Domain.
These are our application models such as input models, data transfer objects, as well as any helpers for mapping Domain models to these. This project will only reference the Domain.Models to help map them to DTOs or other models. This, however, is also optional. You could opt to handle mapping externally when the models are needed in business logic rather than in the Application layer.
These are the definitions for our business logic layer. Services, Managers, etc. There should be no implementation in this project and it should only reference the Application.Models project
This is where we implement our data and business logic.
This is the implementation of our Data access layer. Communicate with 3rd party data providers, local storage, databases, etc. Domain.Interfaces should be implemented here.
This is the implementation of our Business logic layer. Communicate with the data layer through contracts from the Domain.Interfaces. This project should NOT reference the Data project. Application.Interfaces should be implemented here
This is where we implement client logic, set up IoC, create ViewModels, and controls. If we are using Xamarin.Forms, this is where the PCL or Shared Library with Xamarin.Forms is. This Layer can reference any of the above layers in order to wire up IoC. If you have multiple Xamarin.Forms projects in one solution, they can both be here.
This is where reusable controls within Xamarin.Forms exist. Pretty straightforward.
This is the section where the Native Projects live. If you have too many native projects for things like wearables, or the different TV OS’s, then it might make sense to break this section into smaller sections for things like “Apple”, “Google”, “Windows”, or something similar. But for the sake of this demo, we are only working with one project for each platform, so they live together.
This layer should only reference the Client layer and Binding Layer
This is the Xamarin.Android project. If any native services need to be called from one of the shared layers, the IoC set up can be extended into this project and one of the interfaces could be implemented here and registered.
This is the Xamarin.iOS project. As stated above, native services can be set up and injected from here as well.
This is the UWP project. As stated above, native services can be set up and injected from here as well.
This is where Xamarin Binding projects should be if there is a need for binding to any native libraries. As with the Platforms layer, if there are many different binding projects, this layer could be split into different sections for each OS. This layer should be exclusive and not reference any other layer.
This is where UI and Unit tests are. This layer can reference any other level in order to test them. This layer can wire up a completely different IoC container, contain mock projects for simulating any other layer, or any other external reference.