diff --git a/core/lib/Drupal/Core/Extension/CachedModuleHandler.php b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
index 0e98472..b38a1e6 100644
--- a/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
@@ -141,7 +141,7 @@ protected function getImplementationInfo($hook) {
         // function exists on each request to avoid undefined function errors.
         // Since module_hook() may needlessly try to load the include file again,
         // function_exists() is used directly here.
-        if (!function_exists($module . '_' . $hook)) {
+        if (!is_array($this->implementations[$hook][$module]) && !function_exists($module . '_' . $hook)) {
           // Clear out the stale implementation from the cache and force a cache
           // refresh to forget about no longer existing hook implementations.
           unset($this->implementations[$hook][$module]);
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index c510192..61e6347 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -29,6 +29,14 @@ class ModuleHandler implements ModuleHandlerInterface {
   protected $loadedFiles;
 
   /**
+   * Array of implementations parsed from module.implements.yml files.
+   *
+   * @var array
+   *   An associative array keyed by module name of services implementing hooks.
+   */
+  protected $parsedImplementations;
+
+  /**
    * List of enabled bootstrap modules.
    *
    * @var array
@@ -267,6 +275,7 @@ public function resetImplementations() {
     $this->implementations = NULL;
     $this->hookInfo = NULL;
     $this->alterFunctions = NULL;
+    $this->parsedImplementations = NULL;
   }
 
   /**
@@ -305,18 +314,27 @@ public function invoke($module, $hook, $args = array()) {
    */
   public function invokeAll($hook, $args = array()) {
     $return = array();
-    $implementations = $this->getImplementations($hook);
-    foreach ($implementations as $module) {
-      $function = $module . '_' . $hook;
-      if (function_exists($function)) {
-        $result = call_user_func_array($function, $args);
-        if (isset($result) && is_array($result)) {
-          $return = NestedArray::mergeDeep($return, $result);
+    $implementations = $this->getImplementationInfo($hook);
+    foreach ($implementations as $module => $info) {
+      if (is_array($info)) {
+        list($service, $method) = $info;
+        if (($instance = \Drupal::service($service)) &&
+          method_exists($instance, $method)) {
+          $result = call_user_func_array(array($instance, $method), $args);
         }
-        elseif (isset($result)) {
-          $return[] = $result;
+      }
+      else {
+        $function = $module . '_' . $hook;
+        if (function_exists($function)) {
+          $result = call_user_func_array($function, $args);
         }
       }
+      if (isset($result) && is_array($result)) {
+        $return = NestedArray::mergeDeep($return, $result);
+      }
+      elseif (isset($result)) {
+        $return[] = $result;
+      }
     }
 
     return $return;
@@ -459,6 +477,11 @@ protected function getImplementationInfo($hook) {
       if (function_exists($module . '_' . $hook)) {
         $this->implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
       }
+      // Check for a service-based implementation.
+      if (($service_hooks = $this->parseImplements($module)) && !empty($service_hooks[$hook])) {
+        $this->implementations[$hook][$module] = explode(':', $service_hooks[$hook]);
+      }
+
     }
     // Allow modules to change the weight of specific implementations but avoid
     // an infinite loop.
@@ -900,4 +923,29 @@ public function uninstall($module_list = array(), $uninstall_dependents = TRUE)
     return TRUE;
   }
 
+  /**
+   * Parses the module.implements.yml file to discover hook implementations.
+   *
+   * @param string $module
+   *   The module name to parse implementations for.
+   */
+  protected function parseImplements($module) {
+    if (!empty($this->parsedImplementations[$module])) {
+      return $this->parsedImplementations[$module];
+    }
+    $hooks = array();
+    require_once DRUPAL_ROOT . '/core/includes/common.inc';
+    $implements_yaml_file = drupal_get_path('module', $module) . "/$module.implements.yml";
+    if (file_exists($implements_yaml_file)) {
+      $parser = new Parser;
+      $definitions = $parser->parse(file_get_contents($implements_yaml_file));
+      if (isset($definitions['hooks'])) {
+        foreach ($definitions['hooks'] as $id => $definition) {
+          $hooks[$id] = $definition;
+        }
+      }
+    }
+    $this->parsedImplementations[$module] = $hooks;
+    return $this->parsedImplementations[$module];
+  }
 }
diff --git a/core/modules/book/book.implements.yml b/core/modules/book/book.implements.yml
new file mode 100644
index 0000000..0c31439
--- /dev/null
+++ b/core/modules/book/book.implements.yml
@@ -0,0 +1,2 @@
+hooks:
+  node_predelete: 'book.module:nodePreDelete'
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index f548952..b4466b3 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -852,30 +852,6 @@ function book_node_update(EntityInterface $node) {
 }
 
 /**
- * Implements hook_node_predelete().
- */
-function book_node_predelete(EntityInterface $node) {
-  if (!empty($node->book['bid'])) {
-    if ($node->nid == $node->book['bid']) {
-      // Handle deletion of a top-level post.
-      $result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array(
-        ':plid' => $node->book['mlid']
-      ));
-      foreach ($result as $child) {
-        $child_node = node_load($child->nid);
-        $child_node->book['bid'] = $child_node->nid;
-        _book_update_outline($child_node);
-      }
-    }
-    menu_link_delete($node->book['mlid']);
-    db_delete('book')
-      ->condition('mlid', $node->book['mlid'])
-      ->execute();
-    drupal_static_reset('book_get_books');
-  }
-}
-
-/**
  * Implements hook_node_prepare_form().
  */
 function book_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) {
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 9d8c140..48a63cd 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -2,3 +2,6 @@ services:
   book.manager:
     class: Drupal\book\BookManager
     arguments: ['@database', '@plugin.manager.entity']
+  book.module:
+    class: Drupal\book\BookModule
+    arguments: ['@database', '@plugin.manager.entity']
diff --git a/core/modules/book/lib/Drupal/book/BookModule.php b/core/modules/book/lib/Drupal/book/BookModule.php
new file mode 100644
index 0000000..9e301b9
--- /dev/null
+++ b/core/modules/book/lib/Drupal/book/BookModule.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\BookModule
+ */
+
+namespace Drupal\book;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class BookModule {
+
+  /**
+   * Current database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The menu link storage controller.
+   *
+   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   */
+  protected $menuLinkStorage;
+
+  /**
+   * The node storage controller.
+   *
+   * @var \Drupal\node\NodeStorageControllerInterface
+   */
+  protected $nodeStorage;
+
+  /**
+   * Constructs a BookModule object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   Current database connection.
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   Entity manager service.
+   */
+  public function __construct(Connection $connection, EntityManager $entity_manager) {
+    $this->connection = $connection;
+    $this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
+    $this->nodeStorage = $entity_manager->getStorageController('node');
+  }
+
+  /**
+   * Implements hook_node_predelete().
+   */
+  public function nodePreDelete(EntityInterface $node) {
+    if (!empty($node->book['bid'])) {
+      if ($node->nid == $node->book['bid']) {
+        // Handle deletion of a top-level post.
+        $result = $this->connection->query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array(
+          ':plid' => $node->book['mlid']
+        ));
+        foreach ($result as $child) {
+          $child_node = $this->nodeStorage->load($child->nid);
+          $child_node->book['bid'] = $child_node->nid;
+          _book_update_outline($child_node);
+        }
+      }
+      $this->menuLinkStorage->delete(array($this->menuLinkStorage->load($node->book['mlid'])));
+      $this->connection->delete('book')
+        ->condition('mlid', $node->book['mlid'])
+        ->execute();
+      drupal_static_reset('book_get_books');
+    }
+  }
+}
