📜 ⬆️ ⬇️

On-demand synchronization implementation

Over the past decade, the availability of the Internet has increased significantly. Therefore, the number of applications that work in conjunction client-server, also increased significantly. But what to do if access to the network is, but not always? It is with such a requirement from the customer that we faced in one of the projects. All who are interested in the solution developed by us, please under the cat.



Initial data


So, a little more about the task. There are n geographically distant offices. Each office employs several employees, processing data. Data may overlap between offices. In the office most of the time may not have access to the Internet. Accordingly, a situation may arise when the same object was changed by two employees. Since the changes are equivalent and it is impossible to apply one of them and reject the other, there is another requirement - a conflict resolution mechanism. Well, as a cherry on a cake, relatively old laptops with a small amount of RAM are used as workstations in offices.

Before bravely rushing to solve a problem, we, of course, tried to find out whether it would not be cheaper to resolve the issue of network availability than to look for a software solution. But here the customer was adamant and insisted on his internal reasons for such a decision.
')
Leaving the organizational solution, we moved to the technical. Much further depended on the choice of a DBMS for a local DB. The first choice was SQL CE, but after some time, due to big performance problems, the final choice fell on MySql.

If we discard the solutions found in the open spaces of the codeproject, then there were 3 alternatives:


The variant with replication is not suitable for many criteria: starting from the requirements for the visual resolution of conflicts and ending with various DBMS on clients and server (although, of course, this can be solved).

MS Sync Framework inspired great hopes after reading the list of features. After the first acquaintance, the hopes became much less. The main problem was that it was weakly focused on work at the level of domain objects (and this is how it is most convenient for the user to work - to resolve the conflict for the entities with which he works in the application, and not for their individual pieces, which correspond to separate tables).

Therefore, we went to meet the adventure on the way to point 3!

Software model


So, the final architecture included a client implemented on WPF and a server implemented as a WCF service.

For each entity, a class inherited from the base class Entity was created in the domain structure of the application:

public class Entity { public virtual Guid ID { get; set; } public virtual DateTime Timestamp { get; set; } public virtual EEntityState State { get; set; } public virtual bool IsDeleted { get; set; } } public enum EEntityState { Normal, Created, Modified, Deleted } 


Each object in the system has three representations:



Server part


Initially, on the service side for each entity two methods are implemented:


During testing, it was decided to add a third method - GetCount , which would return the number of objects changed after the specified date. The Get method, accordingly, will receive parameters for obtaining N objects with offset M. This was done due to the fact that if the connection to the server was bad, the connection timeout often took off. It would be possible to simply increase the timeout, but the option with paging data seems more correct.

In addition to the methods for processing entities, the following methods are also present on the server:


Conflict resolution


To solve the problem with the resolution of conflicts in the system, the following editing model was implemented.

In the application, each editor can be used in two versions:


The differences between these options are in the form on which editing takes place - when editing an entity, the form contains an editor and save / cancel buttons (some forms may also have additional buttons, but this does not affect the essence of the matter), while saving this form, the object is saved in DB and closing dialogue. When resolving conflicts on the form, two editors are displayed at the same time (for a local object and for an object received from the server), management of the state of the object (deleted / not deleted), besides, it is necessary to visually select the different fields inside the editor, and when closing the window you do not need to save changes in the database.

To do this, in the application, each editor was designed as a separate control. This control contained only logic for editing (calling child editors, updating screen state, etc.). The interface of this editor provided two methods for setting / getting an editable object ( PE ).

 public interface IEditor { event Action EditedObjectChanged; IWindow Window { get; set; } ObjectValidationResult Validate(); void SetEditedObject(PresentationEntity pe); PresentationEntity GetEditedObject(); } 

For ease of use, its generic version was introduced:

 public interface IEditor<TPE> : IEditor where TPE: PresentationEntity { TPE DisplayObject { get; set; } } 

Then he turned into a form that included the logic for translating PE into the domain object and further saving the PE into the database.

The conflict resolution dialog simultaneously displays two editors, in each of which either the PE of the local entity or the PE of the server entity are installed respectively.

For some editors, users had the opportunity to change not only the fields of the object itself, but also the fields of the child objects (for example, editing the child list). Accordingly, as a result of the work of the conflict resolution form, not only the object for which the form was called, but also the objects of the child collections could change its state. Therefore, all objects of the child collections in the form view had the following general class:

 public class ChildListItemPE : PresentationsEntity { public Guid ObjectID { get; set; } public ChildEditorPE EditorPE { get; set; } } 

Where ChildEditorPE is PE for the editor of this entity.

 public class ChildEditorPE : ValidatablePresentationEntity { public Guid ObjectID { get; set; } public EEntityState State { get; set; } } 

Thus, after closing the conflict resolution form, we go through the collections of the edited object (using reflection) and update all objects whose editors have a state other than Normal . As a result, we had a modified object state and object identifiers that were changed while editing the main object.

In addition, during the operation of the conflict resolution form, we call the Noop service method on a timer in a parallel thread to support our session.

The logic of highlighting the state of the form element is based on the assumption that the editors of the local object and the object received from the server have an identical structure, as well as on the condition of a limited number of control types used (i.e. if you want to display differences for the slider, then you need to add code which will check the equality of the current values, as well as set styles for the states).

For each element of the local object editor, a corresponding element is searched for in the object editor received from the server. Next, the presence of the established binding is checked on the corresponding field (for example, Text for TextBox or ItemsSource for DataGrid) - this is another assumption, all fields in editors that affect the state of the object are associated with the model through Binding. Next, we get the values ​​of the fields in the element belonging to the editor of the local object, and in the element belonging to the editor to the object received from the server. Based on the comparison of these values, we establish the necessary style.

Here are some examples of conflict resolution forms.

Elements on the form have a red background color in case the value in the local version of the object differs from the value in the object received from the server.



Lists have a green background if the number of objects and their composition is the same in the local version and in the version obtained from the server. Items in the list also change the background color depending on their state.





Synchronization


The first version of synchronization included the following steps:


In the process, it became necessary to restore the local user base in case of unsuccessful synchronization (for example, the connection with the server was lost, or the user canceled the synchronization). The first option considered is to wrap everything in a transaction. But since in the synchronization process we have access to the database from different threads (interaction with the server is carried out in one thread, the forms work in another (and the forms, when displaying an object in theory, can refer to the database for some reference data from the database), then this option was dropped. Therefore, the synchronization process was supplemented with two steps - in the beginning we create a backup of the local database, and in case of an error / cancellation, we restore the database from the previously created backup.

The next problem we faced was the timeout of the operation while saving the changes on the server - in the case of a large number of changes of entities of the same type, the operation of saving them could take longer than the set timeout allowed. A simple solution would be to increase the timeout. Another solution could be the introduction of paging (by analogy with data acquisition). But we decided to go along an alternative path, which allowed us to resolve the issue with transactionality and on the server side (so that we could apply either all changes from the synchronization session, or not apply any).

All changes made in the Save methods were translated into a JSON object, which was saved to a special table. This object contained information about the session to which it belongs, the type of entity that matches, and the state of the corresponding DTO .

 public class IntermediateItem { public virtual Guid SessionId { get; set; } public virtual string DTOType { get; set; } public virtual string Content { get; set; } } 

After the end of the main part of synchronization (receiving objects, resolving conflicts and sending changes to the server), the client called the CommitChanges service method , which, in turn, sent a message to the Windows service, which already backtracked the changes from the intermediate table to DTO , translating DTO objects in domain entities and their preservation.

The allocation of this operation in a separate win-service was done due to the fact that the preservation operation can take quite a long time. The client at this time periodically calls the server's GetCommitStatus method in order to check the status of the operation.

Instead of conclusion


It is likely that at one time we invented the bicycle. We also understand that the final decision is not without flaws. But in the end we got a stable and workable version of the solution for synchronizing offline parts of the system. But still for future projects (and oddly enough, requests with similar requirements continue to come in regularly) we would be happy to learn other solutions, as well as to improve our model, therefore we will be glad to receive comments and comments.

Disadvantages:


Buns:


Partially listed disadvantages can be eliminated:

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


All Articles