PHP, C#, JavaScript, etc... software develoment

Data synchronization

With the development and advancement of the developed web application may require the support offline mode, and as a consequence, the subsequent synchronization. For example, you developed a web application is accessible on the Internet, but not all clients always have access to the Internet, and they always need to have access to the application or some clients do not have required Internet speed and want to install web application on desktop. This problem is solved:

  • creating a portable version of the application (client)
  • creating a mechanism for obtaining client version of source code changes from the server version
  • adding synchronizing data between a portable version and the version on the Internet (server)

  • In most cases, these three steps is enough. About creating portable web applications on the Internet have enough information. Creating update / upgrade of functionality is simple enough, perhaps it will be complicated by you using redistribution, for example when your server is not a source of new versions of source code, and he gets them from a third server. Synchronizing data between the client and the server is much more complex mechanism, implementation errors that can lead to data loss in database applications. Build Errors architecture synchronization will lead to unnecessary complexity and functionality as a consequence will contribute to the errors in the implementation of its subsequent modifications. Features entity applications require the adaptation of architecture synchronization. As the applications will be added to the new entity will vary between entities in the application, so the functionality of the synchronization must also be easily adapted to new requirements. Light variation and high adaptability to the entities in the strict application architecture - the basic requirements to architecture synchronization.
    While simultaneously changing the same record or what the essence of different users, we have data synchronization conflicts. The resolution of this conflict can be passed on to the user, as is done in most applications, version control (cvs, svn, git), or you can avoid them by creating the appearance of a write lock entity. The first option is suitable for more experienced users or for rarely changing data when the clock frequency is much higher than the frequency of data changes, the second - for a more secure application. For the first option, you must add two fields to each entity:
  • the relevance of records, sync_at
  • a global identifier, gid

  • For the second option has two fields:
  • who blocked the entry, locked_by
  • when a blocked the entry, locked_a

  • The recording can be changed by the user, only when it is locked to them, and its relevance zeroed sync_at. So the client must keep from the server date and time it synchronizes. That's all the necessary changes to existing application to add functionality to synchronize. On the client, Sync will collect and transmit data which sync_at = 0, the server will apply the change only if the received record freeze and collect user account if sync_at more dates of the last synchronization meet the following user. On the server, and Applying of the data obtained sync_at set to the current date and time. Global ID gid entries on the client is always consistent record identifier to the server, that's why it finds a match between records in the database server and the client.

    Base class describing the synchronized nature of SyncTable defines the basic methods of collecting and updating information on the client and server. For scalability, each operation is defined in its class and an instance, returns the corresponding function implements the first version of synchronization: function getGlobalSyncer() { return new SyncTableGlobalSyncer($this); } function getLocalSyncer() { return new SyncTableLocalSyncer($this); } function getGlobalCollector() { return new SyncTableGlobalCollector($this); } function getLocalCollector() { return new SyncTableLocalCollector($this); } Base classes and build information SyncTableGlobalCollector SyncTableLocalCollector extend the base class and implement the interface SyncTableCollector ICollector guarantees the realization of the function collect. Similarly SyncTableGlobalSyncer SyncTableLocalSyncer and extend the base class and implement the interface SyncTableSyncer ISyncer, guaranteeing the implementation of the functions sync. SyncTableCollector implements the basic approach for data collection and supports industry-standard secondary keys. function collect() { if ($this->table->isIdentifierless()) $rows = $this->collectIdentifierless(); else $rows = $this->collectRows(); $this->trs->append($rows); } Similarly SyncTableSyncer implements the basic mechanism for synchronization of sync, and declares an abstract function update the changed data syncRow, for subsequent implementation of the server and client in the classroom and SyncTableGlobalSyncer SyncTableLocalSyncer. function sync(SyncDataSet $sds, SyncTableRowSet $trs) { $this->sds = $sds; $this->trs = $trs; $rmParentIds = $this->table->isIdentifierless()?array():null; foreach ($this->table->getPrimarySyncTables() as $fk => $pst) { $syncWorker->syncTable($pst); if (is_array($rmParentIds)) $rmParentIds[$fk] = $this->sds->get($pst)->getIdMap()->getInnerIds(); } if (!is_array($rmParentIds) && !empty($rmParentIds)) $this->table->getModel()->deleteBy($this->makeInQueryCondition($rmParentIds)); $this->table->getModel()->runRawQuery('SET FOREIGN_KEY_CHECKS = 0;'); $trs->convertGId2LId($sds, $this, $rmParentIds); foreach ($trs->getRows() as $row) $this->syncRow($row); $this->table->getModel()->runRawQuery('SET FOREIGN_KEY_CHECKS = 1;'); } abstract protected function syncRow(array $row, $allowValidation = false); To eliminate the need to analyze the dependencies in a database at the time of synchronization disables the secondary key database.

    Modification of the changed data on the client SyncTableLocalSyncer in the base case looks simple enough: protected function syncRowInsert(array $row, $allowValidation = false) { return $this->table->getModel()->insert($row,true); } protected function syncRowUpdate(array $row, $allowValidation = false) { $lid = FALSE; if (($lid = $this->syncRowUpdateBySecondaryId($row, $allowValidation)) != FALSE) {} elseif (($lid = $this->syncRowUpdateByGidable($row, $allowValidation)) != FALSE) {} elseif (($lid = $this->syncRowUpdateByLocal($row, $allowValidation)) != FALSE) {} return $lid; } protected function syncRow(array $row, $allowValidation = false) { if (($lid = $this->syncRowUpdate($row, $allowValidation)) == FALSE) $lid = $this->syncRowInsert($row,true); $gidFieldName = $this->table->getGidFieldName(); $this->trs->getIdMap()->add($lid, $row[$gidFieldName]); } On the server, due to the presence of entities synchronized in one direction, we must extend sync and make more complicate syncRow to test consistency new data already exists: function sync(SyncDataSet $sds, SyncTableRowSet $trs) { if ($this->table->isTwoWaySync()) { parent::sync($syncWorker, $sds, $trs); } } protected function syncRow(array $row, $allowValidation = false) { $idFieldName = $this->table->getModel()->getIdFieldName(); $lid = $row[$idFieldName]; unset ($row[$idFieldName]); if ($this->table->isSynced()) $row[$this->table->getSyncTsFieldName()] = time(); if ( isset ($row[$this->table->getGidFieldName()]) && $row[$this->table->getGidFieldName()] ) { $gid = $row[$this->table->getGidFieldName()]; if ($allowValidation) { $v = $this->Validate($row, BasePattern::ACTION_UPDATE, $gid); if ($v !== NULL) throw new SyncException($v); } $this->table->getModel()->update($row,$gid); } else { if ($allowValidation) { $v = $this->Validate($row, BasePattern::ACTION_INSERT); if ($v !== NULL) throw new SyncException($v); } $gid = $this->table->getModel()->insert($row, true); } $this->trs->getIdMap()->add($gid, $lid); return $gid; } The presence of interfaces needed to create a fundamentally new mechanism for collecting and updating the changes when you sync, if you need to add support for significantly diverse nature of applications in the future.

    If necessary, extend the functionality, such as support for complex dependencies, SyncTable expanded to the required entity. For example the essence of students requires a block of data before the change, the second version of synchronization: class SyncStudentTable extends SyncTable { const LOCKED_BY = 'locked_by'; const LOCKED_AT = 'locked_at'; function __construct(SyncStruct $syncStruct) { parent::__construct($syncStruct, 'students', true, false); } function getGlobalCollector() { return new SyncStudentTableGlobalCollector( new SyncTableGlobalCollector($this), $this ); } function getLocalCollector() { return new SyncStudentTableLocalCollector( new SyncTableLocalCollector($this), $this ); } function getGlobalSyncer() { return new SyncStudentTableGlobalSyncer($this); } } As a result, for example, gathering information on the server is implemented in the class SyncStudentTableGlobalCollector, ie to make such a synchronization process creates a new class which implements this particular, and the main code does not change. The entity of the User can use the basic approach of modifying the information received from the server, no need to expand SyncTableLocalSyncer, hence the function getLocalSyncer absent in class SyncUserTable. Thus, if in the future or what the essence of the application will be changed and the base case timing for it is not acceptable, then the problem is solved by an extension class SyncTable for that entity and overriding only the required part of the synchronization.

    Extension SyncTable define all nonstandard synchronized entity class hierarchy is as follows. SyncTableWorker SyncTableSyncer SyncTableGlobalSyncer Sync[XXXX1]TableGlobalSyncer Sync[XXXX2]TableGlobalSyncer SyncTableLocalSyncer Sync[XXXX1]TableLocalSyncer Sync[XXXX2]TableLocalSyncer SyncTableCollector SyncTableGlobalCollector Sync[XXXX1]TableGlobalCollector Sync[XXXX2]TableGlobalCollector SyncTableLocalCollector Sync[XXXX1]TableLocalCollector Sync[XXXX2]TableLocalCollector SyncTable Sync[XXXX1]Table Sync[XXXX2]Table Sync[XXXX3]Table

    The resulting architecture class is synchronized entity described in the structure of the synchronization and can be expanded as needed. An abstract class defines the basic functions SyncStruct synchronized structures and for applications it should be extended a final class, for example AppSyncStruct, for example: final class AppSyncStruct extends SyncStruct { function __construct() { $syncTables = array(); $syncTables['courses'] = new SyncTable($this, 'courses', false); $syncTables['students'] = new SyncStudentTable($this); $syncTables['teachers'] = new SyncTeacherTable($this); $syncTables['forms'] = SyncFormTable::create($this) ->addPrimarySyncTable("student_id", $syncTables['students']) ->addPrimarySyncTable("teacher_id", $syncTables['teachers']) $this->addSyncTables($syncTables); $this->addSyncTable(new SyncRostersTable($this)); } } In AppSyncStruct filled with an array of $syncTables, keeps a list of entities synchronized application and pointers to relevant instances of the type SyncTable. Adding a new entity sync application reduces to adding a new element into an array $syncTables, for the simple fact it only takes one line of code, as an entity sdalano courses. Moreover the application can have many different structures sync enough to extend a new class SyncStruct AppNSyncStruct. The synchronization process runs on the server and client classes and SyncLocalWorker SyncGlobalWorker, they extend the abstract class implements the base case SyncWorker synchronization consists in sequence of calls to collect and sync each instance of the array $syncTables.

    Calling code synchronization process looks simple enough and does not depend on what the essence of the application, we synchronize: Client: $struct = new AppSyncStruct(); $request = new SyncRequest($struct); $request->setMetaFields([some information to identify client]); $worker = new SyncLocalWorker($struct, getLatestSyncTime()); $worker->fill($request); $response = SyncResponse::create($request, $struct, $http_responce); $worker->sync($response); Server: $struct = new AppSyncStruct(); $request = new SyncRequest($struct); $request->import($_REQUEST); $worker = new SyncGlobalWorker($request); $syncResponse = new SyncResponse($request, $struct); $worker->sync($syncResponse); echo $syncResponse->toResponse(); If you need to implement another version of synchronization in the application, it is sufficient to create a structure AppNewSyncStruct.

    An example of building a flexible architecture for constructing a mechanism synchronizing data in client / server web applications for example PHP. This architecture allows you to easily and quickly adapt to changing application requirements, it is safe to make changes to synchronize the application entity, changes in the timing of one substance does not need to modify the others and the whole mechanism as a whole. Abstraction of the synchronization of the structure makes it easy to sync entities to build, without duplicating the source code, the required number of realizations of sync.

    That's it.

    Contact universal@vitana-group.com if you have any questions or found a bug.

    Copyright © 2005 - Vitana