diff --git a/core/includes/config.inc b/core/includes/config.inc
index c89537d..ee01f19 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -1,9 +1,9 @@
 <?php
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigSync;
+use Drupal\Core\Config\ConfigSyncInstall;
 use Drupal\Core\Config\FileStorage;
-use Drupal\Core\Config\NullStorage;
-use Drupal\Core\Config\StorageInterface;
 
 /**
  * @file
@@ -24,34 +24,17 @@
  *   The name of the module or theme to install default configuration for.
  */
 function config_install_default_config($type, $name) {
+  // If this module defines any ConfigEntity types, then create a manifest file
+  // for each of them with a listing of the objects it maintains.
+  foreach (config_get_module_config_entities($name) as $entity_type => $entity_info) {
+    config('manifest.' . $entity_info['config_prefix'])->save();
+  }
+
   $config_dir = drupal_get_path($type, $name) . '/config';
   if (is_dir($config_dir)) {
-    $source_storage = new FileStorage($config_dir);
-    $target_storage = drupal_container()->get('config.storage');
-
-    // If this module defines any ConfigEntity types, then create a manifest file
-    // for each of them with a listing of the objects it maintains.
-    foreach (config_get_module_config_entities($name) as $entity_type => $entity_info) {
-      $manifest_config = config('manifest.' . $entity_info['config_prefix']);
-      $manifest_data = array();
-      foreach ($source_storage->listAll($entity_info['config_prefix']) as $config_name) {
-        list(, , $id) = explode('.', $config_name);
-        $manifest_data[$id]['name'] = $config_name;
-      }
-      $manifest_config->setData($manifest_data)->save();
-    }
-
-    $config_changes = array(
-      'delete' => array(),
-      'create' => array(),
-      'change' => array(),
-    );
-    $config_changes['create'] = $source_storage->listAll();
-    if (empty($config_changes['create'])) {
-      return;
-    }
-    $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
-    config_sync_changes($remaining_changes, $source_storage, $target_storage);
+    $config_sync = new ConfigSyncInstall(new FileStorage($config_dir), drupal_container()->get('config.storage'));
+    $config_sync->setChangelist();
+    $config_sync->doSync();
   }
 }
 
@@ -106,97 +89,6 @@ function config($name) {
 }
 
 /**
- * Returns a list of differences between configuration storages.
- *
- * @param Drupal\Core\Config\StorageInterface $source_storage
- *   The storage to synchronize configuration from.
- * @param Drupal\Core\Config\StorageInterface $target_storage
- *   The storage to synchronize configuration to.
- *
- * @return array|bool
- *   An assocative array containing the differences between source and target
- *   storage, or FALSE if there are no differences.
- */
-function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage) {
-  // Config entities maintain 'manifest' files that list the objects they
-  // are currently handling. Each file is a simple indexed array of config
-  // object names. In order to generate a list of objects that have been
-  // created or deleted we need to open these files in both the source and
-  // target storage, generate an array of the objects, and compare them.
-  $source_config_data = array();
-  $target_config_data = array();
-  foreach ($source_storage->listAll('manifest') as $name) {
-    if ($source_manifest_data = $source_storage->read($name)) {
-      $source_config_data = array_merge($source_config_data, $source_manifest_data);
-    }
-
-    if ($target_manifest_data = $target_storage->read($name)) {
-      $target_config_data = array_merge($target_config_data, $target_manifest_data);
-    }
-  }
-
-  $config_changes = array(
-    'create' => array(),
-    'change' => array(),
-    'delete' => array(),
-  );
-
-  foreach (array_diff_key($target_config_data, $source_config_data) as $name => $value) {
-    $config_changes['delete'][] = $value['name'];
-  }
-
-  foreach (array_diff_key($source_config_data, $target_config_data) as $name => $value) {
-    $config_changes['create'][] = $value['name'];
-  }
-
-  foreach (array_intersect($source_storage->listAll(), $target_storage->listAll()) as $name) {
-    // Ignore manifest files
-    if (substr($name, 0, 9) != 'manifest.') {
-      $source_config_data = $source_storage->read($name);
-      $target_config_data = $target_storage->read($name);
-      if ($source_config_data !== $target_config_data) {
-        $config_changes['change'][] = $name;
-      }
-    }
-  }
-
-  // Do not trigger subsequent synchronization operations if there are no
-  // changes in any category.
-  if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
-    return FALSE;
-  }
-  return $config_changes;
-}
-
-/**
- * Writes an array of config file changes from a source storage to a target storage.
- *
- * @param array $config_changes
- *   An array of changes to be written.
- * @param Drupal\Core\Config\StorageInterface $source_storage
- *   The storage to synchronize configuration from.
- * @param Drupal\Core\Config\StorageInterface $target_storage
- *   The storage to synchronize configuration to.
- */
-function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
-  $factory = drupal_container()->get('config.factory');
-  foreach (array('delete', 'create', 'change') as $op) {
-    foreach ($config_changes[$op] as $name) {
-      $config = new Config($name, $target_storage);
-      if ($op == 'delete') {
-        $config->delete();
-      }
-      else {
-        $data = $source_storage->read($name);
-        $config->setData($data ? $data : array());
-        $config->save();
-      }
-      $factory->reset($name);
-    }
-  }
-}
-
-/**
  * Imports configuration into the active configuration.
  *
  * @return bool|null
@@ -205,11 +97,8 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto
  */
 function config_import() {
   // Retrieve a list of differences between staging and the active configuration.
-  $source_storage = drupal_container()->get('config.storage.staging');
-  $target_storage = drupal_container()->get('config.storage');
-
-  $config_changes = config_sync_get_changes($source_storage, $target_storage);
-  if (empty($config_changes)) {
+  $config_sync = new ConfigSync(drupal_container()->get('config.storage.staging'), drupal_container()->get('config.storage'));
+  if (!$config_sync->setChangelist()->hasChanges()) {
     return;
   }
 
@@ -224,8 +113,7 @@ function config_import() {
 
   $success = TRUE;
   try {
-    $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
-    config_sync_changes($remaining_changes, $source_storage, $target_storage);
+    $config_sync->doSync();
   }
   catch (ConfigException $e) {
     watchdog_exception('config_import', $e);
@@ -237,51 +125,6 @@ function config_import() {
 }
 
 /**
- * Invokes MODULE_config_import() callbacks for configuration changes.
- *
- * @param array $config_changes
- *   An array of changes to be loaded.
- * @param Drupal\Core\Config\StorageInterface $source_storage
- *   The storage to synchronize configuration from.
- * @param Drupal\Core\Config\StorageInterface $target_storage
- *   The storage to synchronize configuration to.
- *
- * @todo Add support for other extension types; e.g., themes etc.
- */
-function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
-  // Allow modules to take over configuration change operations for
-  // higher-level configuration data.
-  // First pass deleted, then new, and lastly changed configuration, in order to
-  // handle dependencies correctly.
-  foreach (array('delete', 'create', 'change') as $op) {
-    foreach ($config_changes[$op] as $key => $name) {
-      // Call to the configuration entity's storage controller to handle the
-      // configuration change.
-      $handled_by_module = FALSE;
-      // Validate the configuration object name before importing it.
-      Config::validateName($name);
-      if ($entity_type = config_get_entity_type_by_name($name)) {
-        $old_config = new Config($name, $target_storage);
-        $old_config->load();
-
-        $data = $source_storage->read($name);
-        $new_config = new Config($name, $target_storage);
-        if ($data !== FALSE) {
-          $new_config->setData($data);
-        }
-
-        $method = 'import' . ucfirst($op);
-        $handled_by_module = entity_get_controller($entity_type)->$method($name, $new_config, $old_config);
-      }
-      if (!empty($handled_by_module)) {
-        unset($config_changes[$op][$key]);
-      }
-    }
-  }
-  return $config_changes;
-}
-
-/**
  * Return a list of all config entity types provided by a module.
  *
  * @param string $module
diff --git a/core/lib/Drupal/Core/Config/ConfigSync.php b/core/lib/Drupal/Core/Config/ConfigSync.php
new file mode 100644
index 0000000..b7c3c36
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/ConfigSync.php
@@ -0,0 +1,296 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Config\ConfigSync.
+ */
+
+namespace Drupal\Core\Config;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigSyncEvent;
+use Drupal\Core\Config\StorageInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class ConfigSync {
+
+  /**
+   * The source storage used to discover configuration changes.
+   *
+   * @var Drupal\Core\Config\StorageInterface
+   */
+  protected $sourceStorage;
+
+  /**
+   * The target storage used to write configuration changes.
+   *
+   * @var Drupal\Core\Config\StorageInterface
+   */
+  protected $targetStorage;
+
+  /**
+   * The event dispatcher used to notify subscribers.
+   *
+   * @var Symfony\Component\EventDispatcher\EventDispatcher
+   */
+  protected $eventDispatcher;
+
+  /**
+   * List of changes to synchronize from the source storage to the target
+   * storage.
+   *
+   * @var array
+   */
+  protected $changelist;
+
+  /**
+   * List of changes processed by the doSync().
+   *
+   * @var array
+   */
+  protected $processed;
+
+  /**
+   * Constructs a configuration import object.
+   *
+   * @param Drupal\Core\Config\StorageInterface $sourceStorage
+   *   A storage controller object to use for reading  configuration changes
+   *   from.
+   * @param Drupal\Core\Config\StorageInterface $targetStorage
+   *   A storage controller object to use for writing the configuration changes
+   *   to.
+   * @param Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
+   *   The event dispatcher used to notify subscribers.
+   */
+  public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, EventDispatcher $event_dispatcher = NULL) {
+    $this->sourceStorage = $sourceStorage;
+    $this->targetStorage = $targetStorage;
+    $this->eventDispatcher = $event_dispatcher ? $event_dispatcher : drupal_container()->get('event_dispatcher');
+    $this->processed = $this->changelist = $this->getEmptyChangelist();
+  }
+
+  /**
+   * Gets an empty changelist.
+   *
+   * @return array
+   *   An empty changelist array.
+   */
+  protected function getEmptyChangelist() {
+    return array(
+      'create' => array(),
+      'change' => array(),
+      'delete' => array(),
+    );
+  }
+
+  /**
+   * Gets the list of differences to synchronise.
+   *
+   * @return array
+   *   An array of config changes that are yet to be synchronised.
+   */
+  public function getChangelist() {
+    return $this->changelist;
+  }
+
+  /**
+   * Sets the list of differences to synchronise.
+   *
+   * @return Drupal\Core\Config\ConfigSync
+   *  The ConfigSync instance.
+   */
+  public function setChangelist(array $changelist = NULL) {
+    if ($changelist) {
+      $this->changelist = array_merge($this->changelist, $changelist);
+    }
+    else {
+      $this->changelist = $this->storageChangelist();
+    }
+    return $this;
+  }
+
+  /**
+   * Returns a list of differences between configuration storages.
+   *
+   * @return array
+   *   An associative array containing the differences between source and target
+   *   storage
+   */
+  protected function storageChangelist() {
+    $changelist = $this->getEmptyChangelist();
+    // Config entities maintain 'manifest' files that list the objects they
+    // are currently handling. Each file is a simple indexed array of config
+    // object names. In order to generate a list of objects that have been
+    // created or deleted we need to open these files in both the source and
+    // target storage, generate an array of the objects, and compare them.
+    $source_config_data = array();
+    $target_config_data = array();
+    foreach ($this->sourceStorage->listAll('manifest') as $name) {
+      if ($source_manifest_data = $this->sourceStorage->read($name)) {
+        $source_config_data = array_merge($source_config_data, $source_manifest_data);
+      }
+
+      if ($target_manifest_data = $this->targetStorage->read($name)) {
+        $target_config_data = array_merge($target_config_data, $target_manifest_data);
+      }
+    }
+
+    foreach (array_diff_key($target_config_data, $source_config_data) as $value) {
+      $changelist['delete'][] = $value['name'];
+    }
+
+    foreach (array_diff_key($source_config_data, $target_config_data) as $value) {
+      $changelist['create'][] = $value['name'];
+    }
+
+    foreach (array_intersect($this->sourceStorage->listAll(), $this->targetStorage->listAll()) as $name) {
+      // Ignore manifest files
+      if (substr($name, 0, 9) != 'manifest.') {
+        $source_config_data = $this->sourceStorage->read($name);
+        $target_config_data = $this->targetStorage->read($name);
+        if ($source_config_data !== $target_config_data) {
+          $changelist['change'][] = $name;
+        }
+      }
+    }
+
+    return $changelist;
+  }
+
+  /**
+   * Checks if there is a changelist with changes to process.
+   *
+   * @return bool
+   *   TRUE if there are changes to process and FALSE if not.
+   */
+  public function hasChanges() {
+    // Do not trigger subsequent synchronization operations if there are no
+    // changes in any category.
+    if (empty($this->changelist['create']) && empty($this->changelist['change']) && empty($this->changelist['delete'])) {
+      // There are no changes therefore set change to an empty array.
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Gets list of processed changes.
+   *
+   * @return array
+   *   An array containing a list of processed changes.
+   */
+  public function getProcessed() {
+    return $this->processed;
+  }
+
+  /**
+   * Sets a change as processed.
+   *
+   * @param string $op
+   *   The change operation performed. Either create, change or delete.
+   * @param string $name
+   *   The name of the configuration processed.
+   *
+   */
+  protected function setProcessed($op, $name) {
+    $this->processed[$op][] = $name;
+  }
+
+  /**
+   * Gets a list of unprocessed changes for a given operation.
+   *
+   * @param $op
+   *   The change operation to get the unprocessed list for. Either create,
+   *   change or delete.
+   *
+   * @return array
+   *   An array of configuration names.
+   */
+  protected function getUnprocessed($op) {
+    return array_diff($this->changelist[$op], $this->processed[$op]);
+  }
+
+  /**
+   * Synchronises the changelist to the target storage.
+   *
+   * @return Drupal\Core\Config\ConfigSync
+   *  The ConfigSync instance.
+   */
+  public function doSync() {
+    if ($this->hasChanges()) {
+      $this->doSyncInvokeOwner();
+      $this->doConfigSync();
+      $this->notify('synchronised');
+    }
+    return $this;
+  }
+
+  /**
+   * Writes an array of config changes from the source to the target storage.
+   *
+   * The changelist is modified as each change is processed.
+   */
+  protected function doConfigSync() {
+    $factory = drupal_container()->get('config.factory');
+    foreach (array('delete', 'create', 'change') as $op) {
+      foreach ($this->getUnprocessed($op) as $key => $name) {
+        $config = new Config($name, $this->targetStorage);
+        if ($op == 'delete') {
+          $config->delete();
+        }
+        else {
+          $data = $this->sourceStorage->read($name);
+          $config->setData($data ? $data : array());
+          $config->save();
+        }
+        $factory->reset($name);
+        $this->setProcessed($op, $key);
+      }
+    }
+  }
+
+  /**
+   * Invokes import* methods on configuration entity storage controllers.
+   *
+   * The changelist is modified as each change is processed.
+   *
+   * @todo Add support for other extension types; e.g., themes etc.
+   */
+  protected function doSyncInvokeOwner() {
+    // Allow modules to take over configuration change operations for
+    // higher-level configuration data.
+    // First pass deleted, then new, and lastly changed configuration, in order to
+    // handle dependencies correctly.
+    foreach (array('delete', 'create', 'change') as $op) {
+      foreach ($this->getUnprocessed($op) as $key => $name) {
+        // Call to the configuration entity's storage controller to handle the
+        // configuration change.
+        $handled_by_module = FALSE;
+        // Validate the configuration object name before importing it.
+        Config::validateName($name);
+        if ($entity_type = config_get_entity_type_by_name($name)) {
+          $old_config = new Config($name, $this->targetStorage);
+          $old_config->load();
+
+          $data = $this->sourceStorage->read($name);
+          $new_config = new Config($name, $this->targetStorage);
+          if ($data !== FALSE) {
+            $new_config->setData($data);
+          }
+
+          $method = 'import' . ucfirst($op);
+          $handled_by_module = entity_get_controller($entity_type)->$method($name, $new_config, $old_config);
+        }
+        if (!empty($handled_by_module)) {
+          $this->setProcessed($op, $key);
+        }
+      }
+    }
+  }
+
+  /**
+   * Dispatches a config sync event.
+   */
+  protected function notify($config_event_name) {
+    $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigSyncEvent($this));
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/ConfigSyncEvent.php b/core/lib/Drupal/Core/Config/ConfigSyncEvent.php
new file mode 100644
index 0000000..dffc575
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/ConfigSyncEvent.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\ConfigSyncEvent.
+ */
+
+namespace Drupal\Core\Config;
+
+use Symfony\Component\EventDispatcher\Event;
+use Drupal\Core\Config\ConfigSync;
+
+class ConfigSyncEvent extends Event {
+  /**
+   * Configuration object.
+   *
+   * @var Drupal\Core\Config\ConfigSync
+   */
+  protected $configSync;
+
+  /**
+   * Constructs ConfigSyncEvent.
+   *
+   * @param Drupal\Core\Config\ConfigSync $config_sync
+   *   A config sync object to notify listeners about.
+   */
+  public function __construct(ConfigSync $config_sync) {
+    $this->configSync = $config_sync;
+  }
+
+  /**
+   * Gets the config sync object.
+   *
+   * @return Drupal\Core\Config\ConfigSync
+   *   The ConfigSync object.
+   */
+  public function getConfigSync() {
+    return $this->configSync;
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/ConfigSyncInstall.php b/core/lib/Drupal/Core/Config/ConfigSyncInstall.php
new file mode 100644
index 0000000..8283900
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/ConfigSyncInstall.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\ConfigSyncInstall.
+ */
+
+namespace Drupal\Core\Config;
+
+use Drupal\Core\Config\ConfigSync;
+
+class ConfigSyncInstall extends ConfigSync {
+
+  /**
+   * Returns a changelist list to create a module's default configuration.
+   *
+   * @return array
+   *   An associative array containing the default configuration to create.
+   */
+  protected function storageChangelist() {
+    $changelist = $this->getEmptyChangelist();
+    $changelist['create'] = $this->sourceStorage->listAll();
+    return $changelist;
+  }
+}
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
index c15e02a..cdaa475 100644
--- a/core/modules/config/config.admin.inc
+++ b/core/modules/config/config.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Config\ConfigSync;
 
 /**
  * Helper function to construct the storage changes in a configuration synchronization form.
@@ -28,15 +29,15 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
     return $form;
   }
 
-  $config_changes = config_sync_get_changes($source_storage, $target_storage);
-  if (empty($config_changes)) {
+  $config_sync = new ConfigSync($source_storage, $target_storage);
+  if (!$config_sync->setChangelist()->hasChanges()) {
     $form['no_changes'] = array(
       '#markup' => t('There are no configuration changes.'),
     );
     return $form;
   }
 
-  foreach ($config_changes as $config_change_type => $config_files) {
+  foreach ($config_sync->getChangelist() as $config_change_type => $config_files) {
     if (empty($config_files)) {
       continue;
     }
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
index 95089f7..541dcaf 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\config\Tests;
 
+use Drupal\Core\Config\ConfigSync;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -85,7 +86,8 @@ function testDeleted() {
     $this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
 
     // Verify that there is nothing more to import.
-    $this->assertFalse(config_sync_get_changes($staging, $storage));
+    $config_sync = new ConfigSync($staging, $storage);
+    $this->assertFalse($config_sync->setChangelist()->hasChanges());
   }
 
   /**
@@ -134,7 +136,8 @@ function testNew() {
     $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 
     // Verify that there is nothing more to import.
-    $this->assertFalse(config_sync_get_changes($staging, $storage));
+    $config_sync = new ConfigSync($staging, $storage);
+    $this->assertFalse($config_sync->setChangelist()->hasChanges());
   }
 
   /**
@@ -191,7 +194,8 @@ function testUpdated() {
     $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 
     // Verify that there is nothing more to import.
-    $this->assertFalse(config_sync_get_changes($staging, $storage));
+    $config_sync = new ConfigSync($staging, $storage);
+    $this->assertFalse($config_sync->setChangelist()->hasChanges());
   }
 
 }
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php b/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php
index a33244e..f56f524 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests;
 
+use Drupal\Core\Config\ConfigSync;
 use Drupal\Core\Config\FileStorage;
 
 /**
@@ -39,12 +40,6 @@ public static function importTestViews($class, $modules = array()) {
       $class = get_parent_class($class);
     }
     if (!empty($views)) {
-      $target_storage = drupal_container()->get('config.storage');
-      $config_changes = array(
-        'delete' => array(),
-        'create' => array(),
-        'change' => array(),
-      );
       foreach ($modules as $module) {
         $config_dir = drupal_get_path('module', $module) . '/test_views';
         if (!is_dir($config_dir) || !module_exists($module)) {
@@ -52,16 +47,18 @@ public static function importTestViews($class, $modules = array()) {
         }
 
         $source_storage = new FileStorage($config_dir);
+        $config_sync = new ConfigSync($source_storage, drupal_container()->get('config.storage'));
+        // Get the existing empty change list.
+        $config_changes = $config_sync->getChangelist();
         foreach ($source_storage->listAll() as $config_name) {
           list(, , $id) = explode('.', $config_name);
           if (in_array($id, $views)) {
             $config_changes['create'][] = $config_name;
           }
         }
-      }
-      if (!empty($config_changes['create'])) {
-        $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
-        config_sync_changes($remaining_changes, $source_storage, $target_storage);
+        // Override the changelist with the specific views we need to import.
+        $config_sync->setChangelist($config_changes);
+        $config_sync->doSync();
       }
     }
   }
