📜 ⬆️ ⬇️

Architectural design of mobile applications

Sign of bad design number 1:
Presence of the “god” object with the name containing “Manager”, “Processor” or “API”

The lead iOS developer at Redmadrobot, Egor BepTep Taflanidi, on how to achieve a sleek architectural design of a mobile application using classic design patterns and logical separation of source code into modules.

All the architects whom I met, in varying degrees, had the same professional deformation of character: in their speech they tried to avoid specifics. Such an approach is easy to understand, because the essence of the flexibility of any system is to abstract from concrete solutions. The longer the solution can be delayed - the more flexible the system. If the UI is rather abstracted from the modules of the data retention level, then reading the file from the hard disk can easily be replaced by downloading the same information from the server API.

And yet, let's try to sort through the architectural design of a typical iOS application.
I will describe a global approach to the division of application logic into levels and layers, and also give specific participants in various processes, each with their own responsibility and interactions with the environment.
')

Installation


TL; DR: PROGRAMMERS - ALSO SCIENTIFIC EMPLOYEES

Students of higher educational institutions study natural sciences, study management, acquire knowledge of applied psychology, etc. Each science carries the necessary baggage of accumulated information — academic knowledge, without which the advancement of science itself would be difficult. Without this foundation, there would be no practical benefit from science: it is much more profitable for a process engineer to use time-tested approaches that are guaranteed to produce results than to invent something of his own. Naturally, this foundation itself is constantly evolving and is being completed with new information and practices.

Behind the back of programming is exactly the same strict science as, for example, behind the process of producing aspirin. Informatics, as a typical representative of the formal sciences, has the same set and structure of formal methods , uses established, proven approaches to the study of the new, and, naturally, it has its own baggage of academic knowledge. A scientific approach is guaranteed to lead to a high-quality result, and the knowledge and application of basic software design principles is guaranteed to facilitate product support.

The book "Pattern Language" by Christopher Alexander, which gave impetus to the use of patterns in programming, was released in 1977, and the Model-View-Controller pattern was described in 1979.
Armed with software development and wearing a white theoretician, we will try to build a typical application project for a mobile platform.

NB Most likely, in real life (taking into account the business requirements for the system and taking into account the development timeframe) your project will look slightly different. Nevertheless, it will be possible to say with confidence that behind the back of such a product is not just your invention, but an informed set of solutions.



Given


SPHERICAL SYSTEM IN VACUUM

So, we have:

Hourglass model


SITHOV ALWAYS TWO: ONE BUILS SERVICES, OTHER EXPOSES THE UI

The first step is to separate the model from the controller and the view , thus highlighting two levels of the application: the “bottom” side faces the server capacity, the “top level” looks at the user.

On mobile application projects, the layers of the controller and presentation are usually quite organically capable of being developed by one person, while the rest of the business logic regarding persistence and data processing can be safely transferred to the second developer. Thus, we immediately parallelize the software manufacturing process into two independent streams.

We will adhere to the classical principle, which states that application layers communicate with each other through model objects - classes with a “transparent” implementation consisting exclusively of accessors and mutators (get and set methods; in fact, the class will include only properties). Such classes do not carry with them any logic, and are intended only for storing and moving data.

The central essence of the application - the "neck" of the hourglass - will be the object, otherwise called the "dependency inverter". It will become the only singleton class, and it will bear the sole responsibility - to provide services to the “top” layers of the application.

Services


HISTORY OF PLUG CONNECTION

How are normal server applications made? Very simple. There is always some kind of database (or there may be several of them) in which there are tables. Tables are made up of records. Each entry contains a set of fields in which information is stored.

Records are represented as model objects with fields , thereby creating a logical distinction in the application according to data types: one table - one data type. The “Users” table is the “User” data type (fields: ID, full name, address). The “Messages” table is the “Message” data type (fields: ID, Message Subject, Message Body, To, From, Date). And so on.

Thus, all logic revolves around tables and data types:

1. A model object representing the data type.
2. Serialization of the model object to save it to the table.
3. Deserialization of the model object from the database.
4. Model object transformations: calculation of some statistics,
data sorting, data retrieval, etc.
5. Serialization of the model object for transmission over the network.
6. Deserialization of model objects coming through the network.
7. Web-based web service corresponding to this type
data.

All this is built into a kind of stack , and the server application itself consists of several such independent stacks , each of which corresponds to some type of data.

Roughly speaking, if, according to the API specification, you have the api.domain.com/1-0/messages web service with a classic CRUD interface, this means that there is a “stack” for the “message” data type behind it.

C reate = POST;
R ead = GET;
U pdate = PUT;
D elete = DELETE.

NB There are various interpretations of the POST and PUT statements. In some implementations, POST is responsible for creating entities, in others, for updating. The canonical interpretation, alas, does not exist, but there is nothing terrible in it.

As a rule, the server supports a standardized set of requests, differentiated by URL suffixes to the corresponding web service:

{api} / messages / {id} - operations with an entity by the specified ID;
{api} / messages / {id} / {property} - operations with the {property} field of an entity with ID = {id}.

TOTAL:

All we have to do is make a plug to the outlet.
Namely:

1. Make the application transport layer an entity that will encapsulate an HTTP connection along with all its settings (including security, timeouts, etc.)
• The entity interface must be exactly the same CRUD as the web service exposed on the Internet. If you have access to the address {api} / messages - there must be four corresponding methods; if you have access to GET {api} / messages / {id} / {property} - make a separate method that will receive data from this {property}.
• Entity must be modular. If at one point your wonderful web-service {api} / messages ceases to function, you will only need to implement a single entity that repeats the interface of the transport level , but takes data from
file system.

2. Design parsers and serializers that will generate model objects and, conversely, convert them into a form that is digestible for your transport layer.
Parsers and serializers carry knowledge about how to convert model objects into dictionaries a la JSON or XML. Do not try to fasten this knowledge to the model objects themselves - they do not relate to other layers of the application.
• There is a Russian-language term “topographic converter”, which could replace the English-language “parser” and “serializer”, but the term itself somehow did not take root ...

3. Design the entity that will be responsible for caching.
• An entity should simply be able to provide data access to the data in a safe manner, rewrite and update this data with more recent data.

4. Design the “service” itself - an entity that will be responsible for coordinating the transport layer , parsers , serializers, and cache .
• The service interface must fully comply with the business requirements of the UI layer. If for UI it is necessary to get some kind of entity, there must be a method to get this entity. If the UI needs to save an entity with certain parameters, the service should have a method with these parameters. If the UI requires an array of objects of this type, sorted by some criterion, the service must provide an appropriate method that will return the sorted array.
• And yes, we do not forget the classical principle: application layers communicate with each other through model objects . Using dictionaries / map for data transfer is unacceptable - only strict typing.

Thus, we actually make our own stack, corresponding to the server, but acting in the opposite direction. This is how the sequence of steps that the data needs to overcome to get from the central repository to the end user will look like:

How many types of data - so many services. This figure is quite simple to calculate from the API specification.

NB “Pure” services in nature are not as common. It happens that a service serves several types of data at once. This is due to the fact that model entities can be nested into each other: one entity can carry an array of objects of another type, or have a similar object as a property.

There is nothing terrible in this, just your parsers and serializers should adequately perceive this kind of nesting, and generate objects of the appropriate types.

Services must be as autonomous as possible, which does not prevent you from building logical dependencies between them. For example, it is quite logical that the application has a service that is responsible for authorizing and maintaining the session with the server, and the rest of the services depend on it — they use the token provided to them to form requests.

NB Each of the services will somehow have a CRUD-like interface built around the type of data processed by this service. This can serve as an excellent reason for separating an abstract service with an abstract CRUD interface for subsequent inheritance from it of other services, which will ensure a high degree of code reuse.

Building the model level in this way is the essence of the implementation of the pattern of a layered architecture with the imposition of the ideas of a service-oriented architecture .

We have not invented anything new.

UI level


TIME OF REMARKABLE STORIES

So, you came to the project, downloaded the source code of the developed application from the repository. What is the easiest way to find out what this application does? Right! Run it! Before you are familiar tables with cells, stylized buttons, drums with dates, navigation, screen names ... Now the task: how to relate it all to the source code, which is scattered over several hundred files, that loom in front of you in the IDE?

We have already isolated one of the application levels - in fact, the model can now be a completely independent module that can be reconnected to some other project using the same back-end. Or even reuse for another back-end, if you follow all the design principles and retain a certain level of abstraction.

But here's the bad luck: we still have two (!) Layers, and the only sign of what and where in the source codes is located is the actual running application running on the device or in the emulator. This is where user stories come to the rescue .

If your application fits in any way to common sense, it will follow certain user stories that declare the scenarios necessary for the implementation of a particular action. The most classic example is registration and authorization history . During registration, the user enters some personal data, these data are checked for adequacy, logins and passwords are set, all this can be added with security measures in the form of SMS and so on. Accordingly, in the application there is a whole story concerning registration. Following the logic in order to facilitate your own life, the most obvious solution would be to follow these user stories when building a UI level structure.

Moreover, the iOS application SDKs provide all the necessary tools for this: you just need to create your own Storyboard for each story - and divide the project structure in such a way that all classes are built around these stories . Further, these Storyboards can even be dynamically connected to each other via simple libraries , but these are implementation details.

In the end, you will have a structure divided into modules by history, each module will have its own views and controllers with utility classes, and in this structure you can easily navigate based only on what is happening on your device / emulator screen .

NB And yes, of course, do not forget the classical principles of design. We apply SOLID , DRY , KISS and YAGNI in full, the main thing is not to be lazy and to separate entities in accordance with the responsibility they bear. The rest will be prompted by experience.

You can sketch a rough class diagram:

Naturally, there is no limit to perfection, and this approach does not claim to be a reference.

Conclusion


RESULTS AND DISCUSSION

We briefly examined how to build a slim architectural design of a mobile application. To do this, we applied a fusion of classical design patterns (MVC, SOA and Multilayered Architecture), having also resorted to a logical division of the source code into modules based on user stories .

And now a small question for the community:
Has anyone on combat projects used the V iew-Intractor- P resenter- E ntity- R outer architecture, and how was the reuse of the Router entity / template achieved? How can you avoid the proliferation of Router'a?

Source: https://habr.com/ru/post/246551/


All Articles