diff --git a/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionInterface.php
new file mode 100644
index 0000000..59510dc
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\MergeablePluginDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a mergeable plugin definition.
+ *
+ * @ingroup Plugin
+ */
+interface MergeablePluginDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Merges another definition into this one, using the other for defaults.
+   *
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface $other_definition
+   *   The other definition to merge into $this. It will not override $this, but
+   *   be used to extract default values from instead.
+   *
+   * @return $this
+   */
+  public function mergeDefaultDefinition(PluginDefinitionInterface $other_definition);
+
+  /**
+   * Merges another definition into this one, using the other for overrides.
+   *
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface $other_definition
+   *   The other definition to merge into $this. It will override any values
+   *   already set in $this.
+   *
+   * @return $this
+   */
+  public function mergeOverrideDefinition(PluginDefinitionInterface $other_definition);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
index ed2cc8f..f770c75 100644
--- a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
@@ -5,7 +5,7 @@
 /**
  * Defines a plugin definition.
  *
- * Object-based plugin definitions MUST implement this interface.
+ * Object-based plugin definitions MUST implement this interface. They can also optionally implement any of the following interfaces.
  *
  * @ingroup Plugin
  */
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
new file mode 100644
index 0000000..c67198c
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a deriver.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDeriverDefinitionInterface extends PluginDefinitionInterface, MergeablePluginDefinitionInterface {
+
+  /**
+   * Sets the deriver class.
+   *
+   * @param string $class
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   If the class is invalid.
+   */
+  public function setDeriverClass($class);
+
+  /**
+   * Gets the deriver class.
+   *
+   * @return string|null
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface, or NULL.
+   */
+  public function getDeriverClass();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginInspectionDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginInspectionDefinitionInterface.php
new file mode 100644
index 0000000..0c0a9c4
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginInspectionDefinitionInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\PluginInspectionDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that can identify itself.
+ *
+ * @ingroup Plugin
+ */
+interface PluginInspectionDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the plugin ID.
+   *
+   * @param string $id
+   *   The plugin ID.
+   *
+   * @return static
+   *
+   * @throws \InvalidArgumentException
+   *   If the ID is not a string.
+   */
+  public function setId($id);
+
+  /**
+   * Gets the plugin ID.
+   *
+   * @return string
+   *   The plugin ID.
+   */
+  public function getId();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
index 2028f40..56343bc 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
@@ -2,6 +2,11 @@
 
 namespace Drupal\Component\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\MergeablePluginDefinitionInterface;
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface;
+use Drupal\Component\Plugin\Definition\PluginInspectionDefinitionInterface;
+use Drupal\Component\Plugin\Derivative\DeriverInterface;
 use Drupal\Component\Plugin\Exception\InvalidDeriverException;
 
 /**
@@ -68,6 +73,9 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
         else {
           $plugin_definition = $derivative_plugin_definition;
         }
+        // It is vital that derivative plugin definitions contain derivative
+        // plugin IDs. Enforce it here, because not all derivers do it.
+        $this->setPluginIdOnDefinition($plugin_definition, $plugin_id);
       }
     }
 
@@ -203,35 +211,78 @@ protected function getDeriver($base_plugin_id, $base_definition) {
    */
   protected function getDeriverClass($base_definition) {
     $class = NULL;
-    if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']) && $class = $base_definition['deriver'])) {
-      if (!class_exists($class)) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $base_definition['id'], $class));
+    $plugin_id = NULL;
+    if ($base_definition instanceof PluginDefinitionInterface) {
+      if ($base_definition instanceof PluginInspectionDefinitionInterface) {
+        $plugin_id = $base_definition->getId();
       }
-      if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $base_definition['id'], $class));
+      if ($base_definition instanceof PluginDeriverDefinitionInterface) {
+        $class = $base_definition->getDeriverClass();
       }
     }
+    elseif ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']))) {
+      $plugin_id = $base_definition['id'];
+      $class = $base_definition['deriver'];
+    }
+
+    if (is_null($class)) {
+      return NULL;
+    }
+    elseif (!class_exists($class)) {
+      throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $plugin_id, $class));
+    }
+    elseif (!is_subclass_of($class, DeriverInterface::class)) {
+      throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement %s.', $plugin_id, $class, DeriverInterface::class));
+    }
+
     return $class;
   }
 
   /**
    * Merges a base and derivative definition, taking into account empty values.
    *
-   * @param array $base_plugin_definition
+   * @param mixed[]|\Drupal\Component\Plugin\Definition\PluginDefinitionInterface $base_plugin_definition
    *   The base plugin definition.
-   * @param array $derivative_definition
+   * @param mixed[]|\Drupal\Component\Plugin\Definition\MergeablePluginDefinitionInterface $derivative_definition
    *   The derivative plugin definition.
    *
-   * @return array
+   * @return mixed[]|\Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    *   The merged definition.
    */
   protected function mergeDerivativeDefinition($base_plugin_definition, $derivative_definition) {
-    // Use this definition as defaults if a plugin already defined itself as
-    // this derivative, but filter out empty values first.
-    $filtered_base = array_filter($base_plugin_definition);
-    $derivative_definition = $filtered_base + ($derivative_definition ?: array());
-    // Add back any empty keys that the derivative didn't have.
-    return $derivative_definition + $base_plugin_definition;
+    if (is_array($base_plugin_definition) && is_array($derivative_definition)) {
+      // Use this definition as defaults if a plugin already defined itself as
+      // this derivative, but filter out empty values first.
+      $filtered_base = array_filter($base_plugin_definition);
+      $derivative_definition = $filtered_base + ($derivative_definition ?: array());
+      // Add back any empty keys that the derivative didn't have.
+      return $derivative_definition + $base_plugin_definition;
+    }
+    elseif ($derivative_definition instanceof MergeablePluginDefinitionInterface && $base_plugin_definition instanceof PluginDefinitionInterface) {
+      return $derivative_definition->mergeDefaultDefinition($base_plugin_definition);
+    }
+    else {
+      $base_plugin_definition_type = is_object($base_plugin_definition) ? get_class($base_plugin_definition) : gettype($base_plugin_definition);
+      $derivative_plugin_definition_type = is_object($derivative_definition) ? get_class($derivative_definition) : gettype($derivative_definition);
+      throw new \InvalidArgumentException(sprintf('Could not merge base plugin definitions of type %s into derivative plugin definition of type %s. Both must be arrays, or the derivative plugin definition must implement %s and the base plugin definition must implement %s.', $base_plugin_definition_type, $derivative_plugin_definition_type, MergeablePluginDefinitionInterface::class, PluginDefinitionInterface::class));
+    }
+  }
+
+  /**
+   * Sets the plugin ID on a plugin definition.
+   *
+   * @param mixed[]|\Drupal\Component\Plugin\Definition\PluginInspectionDefinitionInterface $plugin_definition
+   *   The plugin definition.
+   * @param string $plugin_id
+   *   The plugin ID to set.
+   */
+  protected function setPluginIdOnDefinition(&$plugin_definition, $plugin_id) {
+    if ($plugin_definition instanceof PluginInspectionDefinitionInterface) {
+      $plugin_definition->setId($plugin_id);
+    }
+    elseif (is_array($plugin_definition)) {
+      $plugin_definition['id'] = $plugin_id;
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index a63415b..369a9ab 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -391,6 +391,22 @@ public function id() {
   /**
    * {@inheritdoc}
    */
+  public function setId($id) {
+    $this->id = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getProvider() {
     return $this->provider;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index 398347f..f5e6600 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -52,6 +52,8 @@ public function set($property, $value);
    *
    * @return string
    *   The unique identifier of the entity type.
+   *
+   * @deprecated Deprecated as of Drupal 8.0.0. Use self::getId() instead.
    */
   public function id();
 
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
index d81360f..e6ae0be 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
@@ -111,7 +111,6 @@ public function testNonExistentDerivativeFetcher() {
    * @see \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDeriver().\
    *
    * @expectedException \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   * @expectedExceptionMessage Plugin (invalid_discovery) deriver "\Drupal\KernelTests\Core\Plugin\DerivativeTest" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
    */
   public function testInvalidDerivativeFetcher() {
     $definitions = array();
