Application architecture: conceptual layers and arrangements for their use

en-US | created at: 12/18/2017 | published: 12/19/2017 | updated at: 1/1/2018 | number of views: 501

To construct an application architecture is hard. The complexity of the development increases in direct proportion to the developers count who participate in it. In this case, developers should to adhere to predefined rules, templates and arrangements, design patterns, well-known software development methodologies and, in general, OOP principles.

Example of agreements

Let's take as an example the well-known ASP.NET MVC framework. If you worked with it, you probably noticed that the project that creates Visual Studio by default contains the folders Models, Views, Controllers. This is the first arrangement that each type of file (class) is in its own folder.

In the first versions of the ASP.NET MVC framework, the controllers could only be in the Controllers folder. Later this restriction was lifted.

If you try to create a method and then try to open it without creating a View of this method, the system will give you an error message:

Notice where the system tried to find the "lost" view before issuing the message. This behavior is also predetermined by the MVC framework conventions. In this case, the rule states that if the View is not found in the Home Controller folder, you should search it first in the Shared folder, then everything is the same, but with a different extension for another rendering engine (if there are multiple engines connected rendering of markup). In fact, there are many agreements of this kind in ASP.NET MVC. As deeper you dive into the "jungle" of the MVC, than more agreements you will meet.

ASP.NET MVC framework as well as all frameworks, based on the paradigm of "agreement on the configuration". The principle of "Convention over configuration" is generally used for frameworks development, and allows you to reduce the number of required configuration without losing flexibility.

The goal of this article is to show that using the above-mentioned paradigm for collective development can significantly accelerate (simplify) the development of applications, especially if a large number of developers participate in the development. "Configuration agreements" allow solving the issues, which often spend precious minutes, hours and even days and weeks.  

For example, often there is a question, how correctly to name created class, property, a method, the project, a variable, the decision (solution)? How many developers, so many options, if not agree in advance on the rules of naming. And if a new developer has joined the team, how can he start writing the correct code without knowing the rules by which this code is written? Undoubtedly, there are a huge number of auxiliary utilities. For example, StyleCop or Code Style (Visual Studio 2017). The usage of it is simplify the task, but they are powerless in some cases.

I should admit that every year the tasks for developers become complicated because of the complexity of the tasks that business processes. More complex tasks require more comprehensive approaches to their solution. Often, it is not enough to create an application (program, system), it is usually required to support and develop it after the release of the final version. And if in your team there are similar rules and arrangements, it will be much easier for you to find a place in the code, the correct controller and method. Arrangements and naming rules help you navigate the code faster.

History

As an example of increasing complexity, I will cite the evolution of Business Logic patterns that describe how and where the business logic should be re-released. The patterns for the organization of the business level (business logic) over time have undergone significant changes, from simple to complex. Moreover, the evolution did not stop there.

Each of them is described in detail by Martin Fowler, so I will not stop on them in detail. Evolution suggests that programming systems that manipulate data is becoming more complex. It should be understood that the use of design patterns to build the architecture of complex systems greatly simplifies the further support of the system (maintainability) and the introduction of new functionality, but the development become greatly complicated. To simplify this, I propose to use the agreement rules, that is, the "configuration agreement" but as applied to the process of writing code.

Not only among developers, but also among customers can be heard: ORM, repository, business layer, abstraction and dependence, MVVM and others.

There is a longstanding debate among developers as to whether to use ORM as DAL. For example, on the Russian portal sql.ru often such topics are raised. The leitmotif in such disputes sounds the thought: "... for complex projects, the use of ORM significantly reduces the routine ...".

It so happened that for some time I stopped dividing projects into complex and simple ones, taking as a basis the use of ORM for projects of any complexity. Especially when you consider that ORM allows you to use the CodeFirst approach (in particular, EntityFramework).

Dare to assume, that you are already familiar with the design patterns, in particular, with the Repository and UnitOfWork patterns, and I will talk about the arrangements for building a data access layer (Data Access Layer) based on them. And also I will assume that you have an idea of Dependency Injection.

By the way, Using patterns does not guarantee that your code will be written correctly.

Primary rules and conventions for naming

As a matter of fact, we will talk about the agreements, which were developed by the experienced way over a rather long period of development in team. I would like to believe that they will help someone in the work on complex projects. And it would be great if you shared in the comments your own rules and/or arrangements that you and your team use.

I don't get tired of repeating the phrase "everything has already been invented for us" and repeat it in this context. There are already naming conventions in the development world, such as Microsoft or the other in Wikipedia. As a basis for my rules I have taken an agreement from Microsoft. So let's start.

Namespace naming rules

✓ The name of any project must begin with {CompanyName}. This rule is relevant for the developers of the company {CompanyName}.

✓ After the first word {CompanyName}, the name of the project must be specified.

✓ The name of the project must be followed by the name of the platform.

✓ After the specified platform part of the internal architecture of the project.

X do not use underscores, hyphens, or other characters.

X do not use abbreviations and well-known abbreviations

X is not allowed to use numbers in names.

X It is recommended to avoid the use of complex composite names in the specified parts of the naming

Variable naming rules

✓ Use easily readable ID names. For example, a property named HorizontalAlignment is more understandable than alignmenthorizontal.

✓ Readability more important than brevity. The name of the Canscrollhorizontally property is better than the Scrollablex (opaque x-axis reference).

X do not use underscores, hyphens, or other characters.

X It is forbidden to use Hungarian notation.

X avoid using identity names that conflict with the keywords of widely used programming languages.

X do not use acronyms or abbreviations as part of the ID name. For example, use GetWindow instead of GetWin.

X It is forbidden to use acronyms that are not generally accepted and even if they are only if necessary.

✓ Use semantically meaningful names instead of language keywords for type names. For example, GetLength is a name better than GetInt.

✓ Use the generic CLR type name instead of a specific language name, in rare cases where the identifier does not have a semantic value outside of its own type. For example, the conversion to the Int64 method must be named ToInt64, not ToLong (because Int64 is the CLR name for C#, the long alias). The following table shows some of the basic data types using CLR type names (as well as the corresponding type names for C#, Visual Basic, and C++).

✓ Use common names, such as value or item, instead of repeating the type name in rare cases where the identifier has no semantic value and the type of the parameter is irrelevant.

Namespave naming BAD examples

{CompanyName}.ProjectForManagingContent

{CompanyName}.Project.API.V1

{CompanyName}.ProjectForManaging

{CompanyName}.ManagingSystem

{COMPANYNAME}.WEB.PORTAL

MVC.Site.Utils.{CompanyName}

{CompanyName}.Client.System

Namespace naming GOOD examples

For the sample naming solution, I'll take a non-existent http://project.company.ru site. The portal project on the ASP.NET MVC platform must have the following namespace.

Visual Studio Solution name: 

{CompanyName}.Project

Project names by type of affiliation to abstraction level:

{CompanyName}.Project.Contracts

{CompanyName}.Project.Models

{CompanyName}.Project.Data

{CompanyName}.Project.Globalization

{CompanyName}.Project.Utils

{CompanyName}.Project.Android

{CompanyName}.Project.MacOS

{CompanyName}.Project.UWP

{CompanyName}.Project.WebAPI

{CompanyName}.Project.WPF

Названия проектов (projects) при использовании Unit-тестирования:

{CompanyName}.Project.Contracts.Tests

{CompanyName}.Project.Models.Tests

{CompanyName}.Project.Data.Tests

{CompanyName}.Project.Globalization.Tests

{CompanyName}.Project.Utils.Tests

{CompanyName}.Project.Android.Tests

{CompanyName}.Project.MacOS.Tests

{CompanyName}.Project.UWP.Tests

{CompanyName}.Project.API.Tests

{CompanyName}.Project.WPF.Tests

Abstract levels of Data Access Layer

Strictly speaking, we came to the key point of the article. I will give examples for the ASP.NET platform and, in particular, for the MVC 5 project, however, please note that there is nothing to prevent this concept from being used in a WPF application or in some other application, for example, JavaScript applications on based Single Page Application (SPA).

So, in my projects I use the following levels of abstraction for the level of business logic:

These are the basic concepts for most projects, but I must admit that there were projects for which you had to add levels higher than the Manager level. Now let's talk about everything in order. Let's take concrete entities for clarity and make a wrapper for them from the classes for the business level. Suppose that we have a Category, Post, Tag, Comment entity in the project. It looks like the essence for the implementation of the blog? The way it is.

Repository

I hope you read the description in the picture for Repository, I just explain that the basic operations are: reading from the database list, reading from the database of one entity by ID, inserting a new entry into the database, editing the existing entry in the database, deleting the entry. Some Repository may have additional methods, for example, the Clear method for the LogRepository repository. So, for each of the specified entities, we create our own repository: CategoryRepository, PostRepository, TagRepository, CommentRepository.

Naming rule for Repository: the entity name in the singular before the word Repository.

I have a ReadableRepositoryBase <T> class that implements the PagedList <T> () and GetById () methods. Thus, inheriting from this class, the repository the heir acquires the ability to receive a record for the identifier and a collection of pages. And also if the heir from ReadableRepositoryBase <T>, which is called WritableRepositoryBase <T>, has methods Create, Update, Delete. I will not give a specific implementation, because this is not the purpose of my article, but if someone needs it, unsubscribe in the comments.

This approach allows me to inherit from one or from the second abstract class, implementing role access to the entity at the development stage. So, we already have four classes that can manage (CRUD) each with its own essence.

Provider

Further in the hierarchy of levels of the business logic of data access is the Provider. Speaking of it, I will give an example of the relationship between the entities Tag and Post. Let's say that a tag is associated with a blog entry (post) in relation to "many-to-many" and tags for blog entry are required. So, it turns out that when I add a blog entry, I have to somehow handle the tags. This functionality falls under the Unit of Work pattern, which manages several entities centrally. Thus, PostProvider partially implements the Unit of Work pattern.

Naming rule for Provider: The entity name is specified in the singular before the word Provider.

Repoiters as dependencies can be injected to the Providers' constructors. That is, providers manage repositories. Thus, we came to the first rule of Dependency Injection for the levels of Data Access Layer.

Rule Dependency Injection: The constructors of the Provider classes can be injected as dependencies of the Repository objects.

I forgot to mention that DbContext (EntityFramework) or its abstract representative should be added to the Repository classes.

Rule Dependency Injection: DbContext (EntityFramework) or abstraction can be added to the Repository classes.

Manager

The next level is the Manager level. Managers serve to manage related entities (including providers), but at a higher level of abstraction. Let's say that I need to change the visibility of the category that I want to hide. But hiding the category, I should hide a posts in this category. Solving the task, I create a CategoryManager class. In the class I create the SetVisibilityForCategory method. In the PostProvider class, I will create a SetVisibilityForPostsByCategoryId (int categoryId) method that will use the PostRepository class to set the IsVisisble property of all blog entries belonging to the selected category. That is, just change the value of one property, and this is a normal update operation.

Rule Dependency Injection: The Manager classes can be injected as dependencies to the Provider objects.

The key point in this example is that each of the described levels of abstraction implements its own functionality, which could be expanded if required. It is being built, a kind of pyramid, for managing entities. From more "small" operations in Repository to more "large" in Manager. And the "size" of the operation depends on the number of entities that it affects.

Controller

Speaking about ASP.NET MVC, you can not forget about the controllers, which are the basis in the framework around which the entire infrastructure is built. In the MVC-controller (API-controller too), dependencies such as Repository, Provider, Manager can be injected.

Rule Dependency Injection: In the Manager classes, you can also inject dependencies a Repositories.

I want to note that in most cases, the operations that you will call from the controller should be at the Provider and Manager level, because these two levels jointly implement the Unit of Work, and operations from the Repository are often not involved in the business processes of the "large" application.

Naming rule for Controller: the entity name is indicated in the plural before the word Controller.

Service

It is also worth mentioning that usually in applications there are business processes that are not tied to a specific entity. For example, when forming an order for the purchase of a product, you need to send a message to the manager or a notice to the accounting department by e-mail. To implement this functionality, I create an EmailService that implements the sending of an e-mail message.

Rule Dependency Injection: Service classes can be inject into Repository, Provider, Manager classes.

Thus, for each of the listed levels (Repository, Provider, Manager) there may be a need to inject some service. A good example is LogService. Logging is appropriate at any level of logic. In the Repository entry in the log that the record is created. The provider can write data about adding new labels when adding an entry to the blog. The manager can record information that when the category is hidden, all entries in this category are also hidden. In general, everything is fine in moderation.

Conclusion

The presented model of business logic and dependency hierarchy is automatically checked when using the Dependency Injection of the container, because if there is a cycle in the dependencies, an error will be immediately issued (at the extreme in the execution phase).

It is also worth noting that those three levels of abstraction provide ample opportunities for choosing the place of implementation of business logic methods, knowns as distributed business logic. But the decision at what level to implement a specific business process you have to solve on your own, because everything depends on the tasks that you set for yourself.

But in extreme cases, you can ask the architect of the system. :)

No comments yet

What is your name?
for feedback and subscription only
example: https://www.calabonga.com
  1. By sending a comment and providing personal information to the site, you agree with the Privacy Policy , which is used on the site.
  2. All comments are moderated for the presence of idiomatic expressions and obscene words. The link tags will be removed from the message body.