diff --git a/core/lib/Drupal/Core/AnnotationReader.php b/core/lib/Drupal/Core/AnnotationReader.php
new file mode 100644
index 0000000..49a412c
--- /dev/null
+++ b/core/lib/Drupal/Core/AnnotationReader.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\Core;
+
+use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Doctrine\Common\Reflection\StaticReflectionParser;
+
+class AnnotationReader {
+  protected $initialized;
+  protected $reader;
+  protected $loader;
+
+  public function getClassAnnotation($class_name, $annotation_name) {
+    $annotations = $this->getClassAnnotations($class_name);
+    $filtered_annotations = array();
+    foreach ($annotations as $annotation) {
+      if ($annotation instanceOf $annotation_name) {
+        return $annotation;
+      }
+    }
+  }
+
+  public function getClassAnnotations($class_name) {
+    return $this->getFromCache($class_name) ?: $this->doGetClassAnnotations($class_name);
+  }
+
+  protected function doGetClassAnnotations($class_name) {
+    $this->initialize();
+    if (!class_exists($class_name)) {
+      throw new \Exception(sprintf('Entity class %s does not exist!', $class_name));
+    }
+    $reflection_class = new \ReflectionClass($class_name);
+    $annotations = $this->reader()->getClassAnnotations($reflection_class);
+    $this->setCache($class_name, $annotations);
+    return $annotations;
+  }
+
+  protected function doGetClassAnnotationsStatic($class_name) {
+    $this->initialize();
+    $parser = new StaticReflectionParser($class_name, $this->getLoader());
+    $reflection_class = $parser->getReflectionClass();
+    $annotations = $this->reader()->getClassAnnotations($reflection_class);
+    $this->setCache($class_name, $annotations);
+    return $annotations;
+  }
+
+  protected function reader() {
+    if (!isset($this->reader)) {
+      $this->reader = new DoctrineAnnotationReader();
+      foreach ($this->globalIgnoreNames() as $name) {
+        $this->reader->addGlobalIgnoredName($name);
+      }
+    }
+    return $this->reader;
+  }
+
+  public function setReader($reader) {
+    $this->reader = $reader;
+  }
+
+  protected function globalIgnoreNames() {
+    return array('endlink');
+  }
+
+  protected function getFromCache($name) {
+    return isset($this->cache[$name]) ? $this->cache[$name] : FALSE;
+  }
+
+  protected function setCache($name, $value) {
+    $this->cache[$name] = $value;
+  }
+
+  protected function getLoader() {
+    return $this->loader ?: $this->defaultLoader();
+  }
+
+  public function setLoader($loader) {
+    $this->loader = $loader;
+  }
+
+  protected function defaultLoader() {
+    return drupal_classloader();
+  }
+
+  protected function initialize() {
+    if (!$this->initialized) {
+      // If the default loader isn't already callable, it's probably an instance
+      // of the class loader, so make it callable with the right method here.
+      if (!is_callable($loader = $this->getLoader())) {
+        $loader = array($loader, 'loadClass');
+      }
+      AnnotationRegistry::registerLoader($loader);
+      $this->initialized = TRUE;
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 1ce4456..af4b3db 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
@@ -106,19 +107,12 @@ class ConfigStorageController implements EntityStorageControllerInterface, Entit
    * @param \Drupal\Core\Config\StorageInterface $config_storage
    *   The config storage service.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage) {
+  public function __construct($entity_type, EntityType $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage) {
     $this->entityType = $entity_type;
     $this->entityInfo = $entity_info;
     $this->hookLoadArguments = array();
-    $this->idKey = $this->entityInfo['entity_keys']['id'];
-
-    if (isset($this->entityInfo['entity_keys']['status'])) {
-      $this->statusKey = $this->entityInfo['entity_keys']['status'];
-    }
-    else {
-      $this->statusKey = FALSE;
-    }
-
+    $this->idKey = $this->entityInfo->getKey('id');
+    $this->statusKey = $this->entityInfo->getKey('status');
     $this->configFactory = $config_factory;
     $this->configStorage = $config_storage;
   }
@@ -126,7 +120,7 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -225,7 +219,7 @@ public function loadByProperties(array $values = array()) {
    *   The full configuration prefix, for example 'views.view.'.
    */
   public function getConfigPrefix() {
-    return $this->entityInfo['config_prefix'] . '.';
+    return $this->entityInfo->getConfigPrefix() . '.';
   }
 
   /**
@@ -266,7 +260,7 @@ public static function getIDFromConfigName($config_name, $config_prefix) {
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $config_class = $this->entityInfo['class'];
+    $config_class = $this->entityInfo->getClass();
     $prefix = $this->getConfigPrefix();
 
     // Load all of the configuration entities.
@@ -328,7 +322,7 @@ protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
    * Implements Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $class = $this->entityInfo['class'];
+    $class = $this->entityInfo->getClass();
 
     // Set default language to site default if not provided.
     $values += array('langcode' => language_default()->langcode);
diff --git a/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php b/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php
new file mode 100644
index 0000000..6938c9b
--- /dev/null
+++ b/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\Core\Discovery;
+
+interface DiscoverableInterface {
+  public function findAll();
+}
diff --git a/core/lib/Drupal/Core/Discovery/YamlDiscovery.php b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php
new file mode 100644
index 0000000..42005b9
--- /dev/null
+++ b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Core\Discovery;
+
+use Symfony\Component\Yaml\Parser;
+
+class YamlDiscovery implements DiscoverableInterface {
+  public function __construct($name, $directories) {
+    $this->name = $name;
+    $this->directories = $directories;
+  }
+
+  public function findAll() {
+    $parser = $this->parser();
+    $all = array_map(function($file) use ($parser) {
+      return $parser->parse(file_get_contents($file));
+    }, $this->findFiles());
+    return $all;
+  }
+
+  protected function parser() {
+    if (!isset($this->parser)) {
+      $this->parser = new Parser();
+    }
+    return $this->parser;
+  }
+
+  protected function findFiles() {
+    $files = array();
+    foreach ($this->directories as $directory) {
+      $file = $directory . '/' . basename($directory) . '.' . $this->name . '.yml';
+      if (file_exists($file)) {
+        $files[] = $file;
+      }
+    }
+    return $files;
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index 30c8252..5495e2c 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -7,21 +7,19 @@
 
 namespace Drupal\Core\Entity\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
-
 /**
- * Defines an Entity type annotation object.
- *
  * @Annotation
  */
-class EntityType extends Plugin {
+class EntityType {
 
   /**
    * The name of the module providing the type.
    *
    * @var string
    */
-  public $module;
+  public function getModule() {
+    return $this->values['module'];
+  }
 
   /**
    * The name of the entity type class.
@@ -30,7 +28,13 @@ class EntityType extends Plugin {
    *
    * @var string
    */
-  public $class;
+  public function getClass() {
+    return $this->values['class'];
+  }
+
+  public function setClass($class) {
+    return $this->values['class'] = $class;
+  }
 
   /**
    * The name of the entity type's base table.
@@ -39,7 +43,29 @@ class EntityType extends Plugin {
    *
    * @var string
    */
-  public $base_table;
+  public function getBaseTable() {
+    return $this->values['base_table'];
+  }
+
+  public function hasBaseTable() {
+    return isset($this->values['base_table']);
+  }
+
+  public function getDataTable() {
+    return $this->hasDataTable() ? $this->values['data_table'] : FALSE;
+  }
+
+  public function hasDataTable() {
+    return isset($this->values['data_table']);
+  }
+
+  public function hasRevisionTable() {
+    return isset($this->values['revision_table']);
+  }
+
+  public function getRevisionTable() {
+    return $this->hasRevisionTable() ? $this->values['revision_table'] : FALSE;
+  }
 
   /**
    * An associative array where the keys are the names of different controller
@@ -70,16 +96,34 @@ class EntityType extends Plugin {
    *
    * @var array
    */
-  public $controllers = array(
-    'access' => 'Drupal\Core\Entity\EntityAccessController',
-  );
+  public function getControllers() {
+    return $this->values['controllers'] + array(
+      'access' => 'Drupal\Core\Entity\EntityAccessController',
+    );
+  }
+
+  public function getController($controller) {
+    $controllers = $this->getControllers();
+    return $controllers[$controller];
+  }
+
+  public function setController($controller, $value) {
+    return $this->values['controllers'][$controller] = $value;
+  }
+
+  public function hasController($controller) {
+    $controllers = $this->getControllers();
+    return isset($controllers[$controller]);
+  }
 
   /**
    * Boolean indicating whether fields can be attached to entities of this type.
    *
    * @var bool (optional)
    */
-  public $fieldable = FALSE;
+  public function isFieldable() {
+    return isset($this->values['fieldable']) ? $this->values['fieldable'] : FALSE;
+  }
 
   /**
    * Boolean indicating if the persistent cache of field data should be used.
@@ -89,7 +133,9 @@ class EntityType extends Plugin {
    *
    * @var bool (optional)
    */
-  public $field_cache = TRUE;
+  public function fieldsCacheable() {
+    return isset($this->values['field_cache']) ? $this->values['field_cache'] : TRUE;
+  }
 
   /**
    * The human-readable name of the type.
@@ -98,7 +144,17 @@ class EntityType extends Plugin {
    *
    * @var \Drupal\Core\Annotation\Translation
    */
-  public $label;
+  public function getLabel($langcode = NULL) {
+    return $this->values['label']->get();
+       $label = NULL;
+    if (isset($this->values['label_callback']) && function_exists($this->values['label_callback'])) {
+      $label = $this->values['label_callback']($this->values['id'], $this, $langcode);
+    }
+    elseif (!empty($this->values['entity_keys']['label']) && isset($this->{$this->values['entity_keys']['label']})) {
+      $label = $this->{$this->values['entity_keys']['label']};
+    }
+    return $label;
+  }
 
   /**
    * The human-readable name of the entity bundles, e.g. Vocabulary.
@@ -107,7 +163,9 @@ class EntityType extends Plugin {
    *
    * @var \Drupal\Core\Annotation\Translation
    */
-  public $bundle_label;
+  public function getBundleLable() {
+    return $this->values['bundle_label']->get();
+  }
 
   /**
    * The name of a function that returns the label of the entity.
@@ -126,7 +184,17 @@ class EntityType extends Plugin {
    *
    * @var string (optional)
    */
-  public $label_callback;
+  public function getLabelCallback() {
+    return $this->values['label_callback'];
+  }
+
+  public function getUriCallback() {
+    return $this->values['uri_callback'];
+  }
+
+  public function setUriCallback($callback) {
+    return $this->values['uri_callback'] = $callback;
+  }
 
   /**
    * Boolean indicating whether entities should be statically cached during a page request.
@@ -135,7 +203,9 @@ class EntityType extends Plugin {
    *
    * @var bool (optional)
    */
-  public $static_cache = TRUE;
+  public function staticCacheable() {
+    return isset($this->values['static_cache']) ? $this->values['static_cache'] : TRUE;
+  }
 
   /**
    * Boolean indicating whether entities of this type have multilingual support.
@@ -181,10 +251,14 @@ class EntityType extends Plugin {
    *
    * @var array
    */
-  public $entity_keys = array(
-    'revision' => '',
-    'bundle' => '',
-  );
+  public function getKeys() {
+    return $this->values['entity_keys'] + array('revision' => '', 'bundle' => '');
+  }
+
+  public function getKey($key) {
+    $keys = $this->getKeys();
+    return isset($keys[$key]) ? $keys[$key] : FALSE ;
+  }
 
   /**
    * An array describing how the Field API can extract the information it needs
@@ -212,6 +286,10 @@ class EntityType extends Plugin {
    */
   public $route_base_path;
 
+  public function getConfigPrefix() {
+    return $this->values['config_prefix'];
+  }
+
   /**
    * The prefix for the bundles of this entity type.
    *
@@ -262,4 +340,13 @@ class EntityType extends Plugin {
    */
   public $permission_granularity = 'entity_type';
 
+  /**
+   * Constructs a Plugin object.
+   *
+   * Builds up the plugin definition and invokes the get() method for any
+   * classed annotations that were used.
+   */
+  public function __construct($values) {
+    $this->values = $values;
+  }
 }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index bb8e2bb..0a15f9e 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -11,6 +11,7 @@
 use PDO;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Database\Connection;
@@ -126,7 +127,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface, Ent
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -144,7 +145,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database) {
     $this->database = $database;
     $this->entityType = $entity_type;
     $this->entityInfo = $entity_info;
@@ -152,32 +153,21 @@ public function __construct($entity_type, array $entity_info, Connection $databa
     $this->hookLoadArguments = array();
 
     // Check if the entity type supports IDs.
-    if (isset($this->entityInfo['entity_keys']['id'])) {
-      $this->idKey = $this->entityInfo['entity_keys']['id'];
-    }
-    else {
-      $this->idKey = FALSE;
-    }
-
+    $this->idKey = $this->entityInfo->getKey('id');
     // Check if the entity type supports UUIDs.
-    if (!empty($this->entityInfo['entity_keys']['uuid'])) {
-      $this->uuidKey = $this->entityInfo['entity_keys']['uuid'];
-    }
-    else {
-      $this->uuidKey = FALSE;
-    }
+    $this->uuidKey = $this->entityInfo->getKey('uuid');
 
     // Check if the entity type supports revisions.
-    if (!empty($this->entityInfo['entity_keys']['revision'])) {
-      $this->revisionKey = $this->entityInfo['entity_keys']['revision'];
-      $this->revisionTable = $this->entityInfo['revision_table'];
+    $this->revisionKey = $this->entityInfo->getKey('revision');
+    if ($this->revisionKey) {
+      $this->revisionTable = $this->entityInfo->getRevisionTable();
     }
     else {
       $this->revisionKey = FALSE;
     }
 
     // Check if the entity type supports static caching of loaded entities.
-    $this->cache = !empty($this->entityInfo['static_cache']);
+    $this->cache = $this->entityInfo->staticCacheable();
   }
 
   /**
@@ -223,11 +213,12 @@ public function load(array $ids = NULL) {
       // Build and execute the query.
       $query_result = $this->buildQuery($ids)->execute();
 
-      if (!empty($this->entityInfo['class'])) {
+      $entity_class = $this->entityInfo->getClass();
+      if (!empty($entity_class)) {
         // We provide the necessary arguments for PDO to create objects of the
         // specified entity class.
         // @see Drupal\Core\Entity\EntityInterface::__construct()
-        $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+        $query_result->setFetchMode(PDO::FETCH_CLASS, $entity_class, array(array(), $this->entityType));
       }
       $queried_entities = $query_result->fetchAllAssoc($this->idKey);
     }
@@ -358,7 +349,8 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $base_table = $this->entityInfo->getBaseTable();
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
 
     $query->addTag($this->entityType . '_load_multiple');
 
@@ -370,7 +362,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($base_table);
 
     if ($this->revisionKey) {
       // Add all fields from the {entity_revision} table.
@@ -420,7 +412,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
    */
   protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     // Attach fields.
-    if ($this->entityInfo['fieldable']) {
+    if ($this->entityInfo->isFieldable()) {
       if ($load_revision) {
         field_attach_load_revision($this->entityType, $queried_entities);
       }
@@ -475,7 +467,7 @@ protected function cacheSet($entities) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $class = $this->entityInfo['class'];
+    $class = $this->entityInfo->getClass();
 
     $entity = new $class($values, $this->entityType);
 
@@ -690,7 +682,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($entity);
     }
     // Invoke the hook.
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 91ec62d..f7fdb23 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -12,6 +12,7 @@
 
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\TypedData\ComplexDataInterface;
@@ -54,21 +55,21 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
   /**
    * Overrides DatabaseStorageController::__construct().
    */
-  public function __construct($entity_type, array $entity_info, Connection $database) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database) {
     parent::__construct($entity_type,$entity_info, $database);
-    $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE;
-    $this->entityClass = $this->entityInfo['class'];
+    $this->bundleKey = $this->entityInfo->getKey('bundle');
+    $this->entityClass = $this->entityInfo->getClass();
 
     // Check if the entity type has a dedicated table for properties.
-    if (!empty($this->entityInfo['data_table'])) {
-      $this->dataTable = $this->entityInfo['data_table'];
+    if ($data_table = $this->entityInfo->getDataTable()) {
+      $this->dataTable = $data_table;
     }
 
     // Work-a-round to let load() get stdClass storage records without having to
     // override it. We map storage records to entities in
     // DatabaseStorageControllerNG:: mapFromStorageRecords().
     // @todo: Remove this once this is moved in the main controller.
-    unset($this->entityInfo['class']);
+    //unset($this->entityInfo['class']);
   }
 
   /**
@@ -158,7 +159,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    * {@inheritdoc}
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
     $is_revision_query = $this->revisionKey && ($revision_id || !$this->dataTable);
 
     $query->addTag($this->entityType . '_load_multiple');
@@ -171,11 +172,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($this->entityInfo->getBaseTable());
 
     if ($is_revision_query) {
       // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo['revision_table']));
+      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()));
       // The ID field is provided by entity, so remove it.
       unset($entity_revision_fields[$this->idKey]);
 
@@ -212,7 +213,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     // Map the loaded stdclass records into entity objects and according fields.
     $queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision);
 
-    if ($this->entityInfo['fieldable']) {
+    if ($this->entityInfo->isFieldable()) {
       if ($load_revision) {
         field_attach_load_revision($this->entityType, $queried_entities);
       }
@@ -303,10 +304,10 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
       $data = $query->execute();
       $field_definition = $this->getFieldDefinitions(array());
       if ($this->revisionTable) {
-        $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
+        $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()), drupal_schema_fields_sql($this->entityInfo->getBaseTable())));
       }
       else {
-        $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
+        $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo->getDataTable()));
       }
 
       foreach ($data as $values) {
@@ -359,10 +360,10 @@ public function save(EntityInterface $entity) {
 
       // Create the storage record to be saved.
       $record = $this->mapToStorageRecord($entity);
-
+      $base_table = $this->entityInfo->getBaseTable();
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityInfo['base_table'], $record, $this->idKey);
+          $return = drupal_write_record($base_table, $record, $this->idKey);
         }
         else {
           // @todo, should a different value be returned when saving an entity
@@ -380,7 +381,7 @@ public function save(EntityInterface $entity) {
         $this->invokeHook('update', $entity);
       }
       else {
-        $return = drupal_write_record($this->entityInfo['base_table'], $record);
+        $return = drupal_write_record($base_table, $record);
         $entity->{$this->idKey}->value = $record->{$this->idKey};
         if ($this->revisionKey) {
           $record->{$this->revisionKey} = $this->saveRevision($entity);
@@ -450,7 +451,7 @@ protected function saveRevision(EntityInterface $entity) {
       if ($entity->isNewRevision()) {
         drupal_write_record($this->revisionTable, $record);
         if ($entity->isDefaultRevision()) {
-          $this->database->update($this->entityInfo['base_table'])
+          $this->database->update($this->entityInfo->getBaseTable())
             ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
             ->condition($this->idKey, $record->{$this->idKey})
             ->execute();
@@ -507,7 +508,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($entity);
     }
 
@@ -528,7 +529,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
    */
   protected function mapToStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
-    foreach (drupal_schema_fields_sql($this->entityInfo['base_table']) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->getBaseTable()) as $name) {
       $record->$name = $entity->$name->value;
     }
     return $record;
@@ -546,7 +547,7 @@ protected function mapToStorageRecord(EntityInterface $entity) {
   protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) {
     $record = new \stdClass();
     $definitions = $entity->getPropertyDefinitions();
-    foreach (drupal_schema_fields_sql($this->entityInfo['revision_table']) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->getRevisionTable()) as $name) {
       if (isset($definitions[$name]) && isset($entity->$name->value)) {
         $record->$name = $entity->$name->value;
       }
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 7ea8b62..c140c65 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -135,15 +135,7 @@ public function bundle() {
    * Implements \Drupal\Core\Entity\EntityInterface::label().
    */
   public function label($langcode = NULL) {
-    $label = NULL;
-    $entity_info = $this->entityInfo();
-    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
-      $label = $entity_info['label_callback']($this->entityType, $this, $langcode);
-    }
-    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
-      $label = $this->{$entity_info['entity_keys']['label']};
-    }
-    return $label;
+    return $this->entityInfo()->getLabel();
   }
 
   /**
@@ -306,7 +298,7 @@ public function getTranslationLanguages($include_default = TRUE) {
     $languages = array($default_language->langcode => $default_language);
     $entity_info = $this->entityInfo();
 
-    if ($entity_info['fieldable']) {
+    if ($entity_info->isFieldable()) {
       // Go through translatable properties and determine all languages for
       // which translated values are available.
       foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) {
diff --git a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
index 79232c1..75b2af8 100644
--- a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -38,6 +39,6 @@
    * @return static
    *   A new instance of the entity controller.
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info);
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info);
 
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 7c5f7ed..d64de9f 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -147,7 +147,7 @@ public function form(array $form, array &$form_state) {
     // @todo Exploit the Field API to generate the default widgets for the
     // entity properties.
     $info = $entity->entityInfo();
-    if (!empty($info['fieldable'])) {
+    if ($info->isfieldable()) {
       field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 24e7890..cc9a06b 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -7,13 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Entity\EntityTypes;
 use Drupal\Core\Language\Language;
-use Drupal\Core\Plugin\Discovery\AlterDecorator;
-use Drupal\Core\Plugin\Discovery\CacheDecorator;
-use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
-use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -31,7 +26,7 @@
  * @see entity_get_info()
  * @see hook_entity_info_alter()
  */
-class EntityManager extends PluginManagerBase {
+class EntityManager {
 
   /**
    * The injection container that should be passed into the controller factory.
@@ -47,6 +42,7 @@ class EntityManager extends PluginManagerBase {
    */
   protected $controllers = array();
 
+  protected $entity_types;
   /**
    * Constructs a new Entity plugin manager.
    *
@@ -61,15 +57,35 @@ public function __construct(\Traversable $namespaces, ContainerInterface $contai
     $annotation_namespaces = array(
       'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
     );
-    $this->discovery = new AnnotatedClassDiscovery('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
-    $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
-    $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
-    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . language(Language::TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
-
-    $this->factory = new DefaultFactory($this->discovery);
+    $this->annotation_reader = new \Drupal\Core\AnnotationReader('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
     $this->container = $container;
   }
 
+  public function getDefinitions() {
+    return $this->entityTypes()->findAll();
+  }
+
+  public function getDefinition($name) {
+    return $this->entityTypes()->findByName($name);
+  }
+
+  public function clearCachedDefinitions() {
+  }
+
+  protected function entityTypes() {
+    if (!isset($this->entity_types)) {
+      $this->entity_types = new EntityTypes;
+    }
+    return $this->entity_types;
+  }
+
+  protected function getAnnotationReader() {
+    if (!isset($this->annotation_reader)) {
+      $this->annotation_reader = new \Drupal\Core\AnnotationReader();
+    }
+    return $this->annotation_reader;
+  }
+
   /**
    * Checks whether a certain entity type has a certain controller.
    *
@@ -82,8 +98,7 @@ public function __construct(\Traversable $namespaces, ContainerInterface $contai
    *   Returns TRUE if the entity type has the controller, else FALSE.
    */
   public function hasController($entity_type, $controller_type) {
-    $definition = $this->getDefinition($entity_type);
-    return !empty($definition['controllers'][$controller_type]);
+    return $this->getDefinition($entity_type)->hasController($controller_type);
   }
 
   /**
@@ -102,7 +117,7 @@ public function hasController($entity_type, $controller_type) {
    */
   public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
     $definition = $this->getDefinition($entity_type);
-    $definition = $definition['controllers'];
+    $definition = $definition->getControllers();
     if (empty($definition[$controller_type])) {
       throw new \InvalidArgumentException(sprintf('The entity (%s) did not specify a %s.', $entity_type, $controller_type));
     }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypes.php b/core/lib/Drupal/Core/Entity/EntityTypes.php
new file mode 100644
index 0000000..a7c0d0f
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypes.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Discovery\YamlDiscovery;
+
+class EntityTypes {
+  protected $types = array();
+  protected $class_mapping = array();
+  protected $file_finder;
+
+  public function findAll() {
+    if (empty($this->types)) {
+      $class_mappings = $this->loadMappingsFromFiles();
+      foreach ($class_mappings as $name => $class) {
+        $this->types[$name] = $this->getAnnotation($class);
+      }
+      $this->moduleHandler()->alter('entity_info', $this->types);
+    }
+    return $this->types;
+  }
+
+  public function findByName($name) {
+    if (!isset($this->types[$name])) {
+      $class_mappings = $this->loadMappingsFromFiles();
+      $class = $class_mappings[$name];
+      if ($type = $this->getAnnotation($class)) {
+        $this->types[$name] = $this->getAnnotation($class);
+      }
+      else {
+        throw new \RuntimeException(sprintf("No such entity type %s!", $type));
+      }
+    }
+    return $this->types[$name];
+  }
+
+  public function loadMappingsFromFiles() {
+    if (empty($this->class_mapping)) {
+      $all = $this->fileFinder()->findAll();
+      $this->class_mapping = call_user_func_array("array_merge", $all);
+    }
+    return $this->class_mapping;
+  }
+
+  protected function name() {
+    return 'entity';
+  }
+
+  protected function getAnnotation($classname) {
+    $entity_type = $this->getAnnotationReader()
+      ->getClassAnnotation($classname, 'Drupal\Core\Entity\Annotation\EntityType');
+    $entity_type->setClass($classname);
+    return $entity_type;
+  }
+
+  protected function fileFinder() {
+    if (!isset($this->file_finder)) {
+      $this->file_finder = new YamlDiscovery($this->name(), $this->directories());
+    }
+    return $this->file_finder;
+  }
+
+  protected function directories() {
+    return $this->moduleHandler()->getModuleDirectories();
+  }
+
+  protected function moduleHandler() {
+    return \Drupal::moduleHandler();
+  }
+
+  protected function getAnnotationReader() {
+    if (!isset($this->annotation_reader)) {
+      $this->annotation_reader = new \Drupal\Core\AnnotationReader();
+    }
+    return $this->annotation_reader;
+  }
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 3a5ffbe..75ca27e 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -890,4 +890,11 @@ public function uninstall($module_list = array(), $uninstall_dependents = TRUE)
     return TRUE;
   }
 
+  public function getModuleDirectories() {
+    $dirs = array();
+    foreach ($this->getModuleList() as $module => $filename) {
+      $dirs[$module] = dirname($filename);
+    }
+    return $dirs;
+  }
 }
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
index f28906f..f252337 100644
--- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
+++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
@@ -71,8 +71,6 @@ public function process(array &$variables, Route $route, array &$converted) {
     $options = $route->getOptions();
     $configuredTypes = isset($options['converters']) ? $options['converters'] : array();
 
-    $entityTypes = array_keys($this->entityManager->getDefinitions());
-
     foreach ($variable_names as $name) {
       // Do not process this variable if it's already marked as converted.
       if (in_array($name, $converted)) {
@@ -88,7 +86,7 @@ public function process(array &$variables, Route $route, array &$converted) {
         $type = $name;
       }
 
-      if (in_array($type, $entityTypes)) {
+      if ($this->entityManager->getDefinition($type)) {
         $value = $variables[$name];
 
         $storageController = $this->entityManager->getStorageController($type);
diff --git a/core/modules/block/block.entity.yml b/core/modules/block/block.entity.yml
new file mode 100644
index 0000000..a574c3f
--- /dev/null
+++ b/core/modules/block/block.entity.yml
@@ -0,0 +1 @@
+block: Drupal\block\Plugin\Core\Entity\Block
diff --git a/core/modules/block/custom_block/custom_block.entity.yml b/core/modules/block/custom_block/custom_block.entity.yml
new file mode 100644
index 0000000..bc2223d
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.entity.yml
@@ -0,0 +1,2 @@
+custom_block: Drupal\custom_block\Plugin\Core\Entity\CustomBlock
+
diff --git a/core/modules/breakpoint/breakpoint.entity.yml b/core/modules/breakpoint/breakpoint.entity.yml
new file mode 100644
index 0000000..278b03f
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.entity.yml
@@ -0,0 +1,2 @@
+breakpoint: Drupal\breakpoint\Plugin\Core\Entity\Breakpoint
+breakpoint_group: Drupal\breakpoint\Plugin\Core\Entity\BreakpointGroup
diff --git a/core/modules/comment/comment.entity.yml b/core/modules/comment/comment.entity.yml
new file mode 100644
index 0000000..014f640
--- /dev/null
+++ b/core/modules/comment/comment.entity.yml
@@ -0,0 +1 @@
+comment: Drupal\comment\Plugin\Core\Entity\Comment
diff --git a/core/modules/contact/contact.entity.yml b/core/modules/contact/contact.entity.yml
new file mode 100644
index 0000000..2e5325f
--- /dev/null
+++ b/core/modules/contact/contact.entity.yml
@@ -0,0 +1,2 @@
+contact_category: Drupal\contact\Plugin\Core\Entity\Category
+contact_message: Drupal\contact\Plugin\Core\Entity\Message
diff --git a/core/modules/entity/entity.entity.yml b/core/modules/entity/entity.entity.yml
new file mode 100644
index 0000000..230d67a
--- /dev/null
+++ b/core/modules/entity/entity.entity.yml
@@ -0,0 +1 @@
+entity_form_display: Drupal\entity\Plugin\Core\Entity\EntityFormDisplay
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index baa7ff4..169e226 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -893,7 +893,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
   $info = entity_get_info($entity_type);
   // Only the most current revision of non-deleted fields for cacheable entity
   // types can be cached.
-  $cache_read = $load_current && $info['field_cache'] && empty($options['deleted']);
+  $cache_read = $load_current && $info->fieldsCacheable() && empty($options['deleted']);
   // In addition, do not write to the cache when loading a single field.
   $cache_write = $cache_read && !isset($options['field_id']);
 
diff --git a/core/modules/field/field.entity.yml b/core/modules/field/field.entity.yml
new file mode 100644
index 0000000..fb86419
--- /dev/null
+++ b/core/modules/field/field.entity.yml
@@ -0,0 +1,2 @@
+field_entity: Drupal\field\Plugin\Core\Entity\Field
+field_instance: Drupal\field\Plugin\Core\Entity\FieldInstance
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index a720c4d..675d538 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -252,7 +252,7 @@ function field_data_type_info() {
  */
 function field_entity_create(EntityInterface $entity) {
   $info = $entity->entityInfo();
-  if (!empty($info['fieldable'])) {
+  if ($info->isFieldable()) {
     foreach ($entity->getTranslationLanguages() as $langcode => $language) {
       field_populate_default_values($entity, $langcode);
     }
diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc
index b01e882..042771e 100644
--- a/core/modules/field/field.views.inc
+++ b/core/modules/field/field.views.inc
@@ -115,7 +115,7 @@ function field_views_field_default_views_data($field) {
   // Build the relationships between the field table and the entity tables.
   foreach ($field['bundles'] as $entity => $bundles) {
     $entity_info = entity_get_info($entity);
-    $groups[$entity] = $entity_info['label'];
+    $groups[$entity] = $entity_info->getLabel();
 
     // Override Node to Content.
     if ($groups[$entity] == t('Node')) {
@@ -127,18 +127,22 @@ function field_views_field_default_views_data($field) {
       $group_name = $groups[$entity];
     }
 
-    if (!isset($entity_info['base_table'])) {
+    if (!$entity_info->hasBaseTable()) {
       continue;
     }
-    $entity_tables[$entity_info['base_table']] = $entity;
-    $current_tables[$entity] = $entity_info['base_table'];
-    if (isset($entity_info['revision_table'])) {
-      $entity_tables[$entity_info['revision_table']] = $entity;
-      $revision_tables[$entity] = $entity_info['revision_table'];
+    $base_table = $entity_info->getBaseTable();
+    $entity_tables[$base_table] = $entity;
+    $current_tables[$entity] = $base_table;
+
+    // TODO: Working around a bug, this should be the opposite check
+    if (!$entity_info->hasRevisionTable()) {
+      $revision_table = $entity_info->getRevisionTable();
+      $entity_tables[$revision_table] = $entity;
+      $revision_tables[$entity] = $revision_table;
     }
 
-    $data[$current_table]['table']['join'][$entity_info['base_table']] = array(
-      'left_field' => $entity_info['entity_keys']['id'],
+    $data[$current_table]['table']['join'][$base_table] = array(
+      'left_field' => $entity_info->getKey('id'),
       'field' => 'entity_id',
       'extra' => array(
         array('field' => 'entity_type', 'value' => $entity),
@@ -146,9 +150,9 @@ function field_views_field_default_views_data($field) {
       ),
     );
 
-    if (!empty($entity_info['entity_keys']['revision']) && !empty($entity_info['revision_table'])) {
-      $data[$revision_table]['table']['join'][$entity_info['revision_table']] = array(
-        'left_field' => $entity_info['entity_keys']['revision'],
+    if ($revision_key = $entity_info->getKey('revision') && !empty($revision_table)) {
+      $data[$revision_table]['table']['join'][$revision_table] = array(
+        'left_field' => $revision_key,
         'field' => 'revision_id',
         'extra' => array(
           array('field' => 'entity_type', 'value' => $entity),
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
index 930d367..901e8ad 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
@@ -117,19 +117,19 @@ public function execute() {
   protected function prepare() {
     $entity_type = $this->entityType;
     $this->entityInfo = $this->entityManager->getDefinition($entity_type);
-    if (!isset($this->entityInfo['base_table'])) {
+    if (!($base_table = $this->entityInfo->getBaseTable())) {
       throw new QueryException("No base table, invalid query.");
     }
-    $base_table = $this->entityInfo['base_table'];
+    $base_table = $base_table;
     $simple_query = TRUE;
-    if (isset($this->entityInfo['data_table'])) {
+    if ($this->entityInfo->getDataTable()) {
       $simple_query = FALSE;
     }
     $this->sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
     $this->sqlQuery->addMetaData('entity_type', $entity_type);
-    $id_field = $this->entityInfo['entity_keys']['id'];
+    $id_field = $this->entityInfo->getKey('id');
     // Add the key field for fetchAllKeyed().
-    if (empty($this->entityInfo['entity_keys']['revision'])) {
+    if (!($this->entityInfo->getKey('revision'))) {
       // When there is no revision support, the key field is the entity key.
       $this->sqlFields["base_table.$id_field"] = array('base_table', $id_field);
       // Now add the value column for fetchAllKeyed(). This is always the
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
index a387cf4..73d2bb7 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
@@ -84,16 +84,16 @@ function addField($field, $type, $langcode) {
     for ($key = 0; $key <= $count; $key ++) {
       // If there is revision support and only the current revision is being
       // queried then use the revision id. Otherwise, the entity id will do.
-      if (!empty($entity_info['entity_keys']['revision']) && $age == FIELD_LOAD_CURRENT) {
+      if ($revision_key = $entity_info->getKey('revision') && $age == FIELD_LOAD_CURRENT) {
         // This contains the relevant SQL field to be used when joining entity
         // tables.
-        $entity_id_field = $entity_info['entity_keys']['revision'];
+        $entity_id_field = $revision_key;
         // This contains the relevant SQL field to be used when joining field
         // tables.
         $field_id_field = 'revision_id';
       }
       else {
-        $entity_id_field = $entity_info['entity_keys']['id'];
+        $entity_id_field = $entity_info->getKey('id');
         $field_id_field = 'entity_id';
       }
       // This can either be the name of an entity property (non-configurable
@@ -165,11 +165,12 @@ function addField($field, $type, $langcode) {
         // finds the property first. The data table is prefered, which is why
         // it gets added before the base table.
         $entity_tables = array();
-        if (isset($entity_info['data_table'])) {
+        if ($data_table = $entity_info->getDataTable()) {
           $this->sqlQuery->addMetaData('simple_query', FALSE);
-          $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']);
+          $entity_tables[$entity_info['data_table']] = drupal_get_schema($data_table);
         }
-        $entity_tables[$entity_info['base_table']] = drupal_get_schema($entity_info['base_table']);
+        $base_table = $entity_info->getBaseTable();
+        $entity_tables[$base_table] = drupal_get_schema($base_table);
         $sql_column = $specifier;
         $table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables);
       }
diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc
index 77c4ea3..b8746a0 100644
--- a/core/modules/image/image.views.inc
+++ b/core/modules/image/image.views.inc
@@ -41,7 +41,7 @@ function image_field_views_data_views_data_alter(&$data, $field) {
     $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
 
     list($label, $all_labels) = field_views_field_label($field['field_name']);
-    $entity = $entity_info['label'];
+    $entity = $entity_info->getLabel();
     if ($entity == t('Node')) {
       $entity = t('Content');
     }
@@ -53,8 +53,8 @@ function image_field_views_data_views_data_alter(&$data, $field) {
       'field_name' => $field['field_name'],
       'field table' => _field_sql_storage_tablename($field),
       'field field' => $field['field_name'] . '_fid',
-      'base' => $entity_info['base_table'],
-      'base field' => $entity_info['entity_keys']['id'],
+      'base' => $entity_info->getBaseTable(),
+      'base field' => $entity_info->getKey('id'),
       'label' => t('!field_name', array('!field_name' => $field['field_name'])),
       'join_extra' => array(
         0 => array(
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 14c41b0..f3f6bf8 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -145,11 +145,11 @@ function menu_menu() {
  * Implements hook_entity_info_alter().
  */
 function menu_entity_info_alter(&$entity_info) {
-  $entity_info['menu']['controllers']['list'] = 'Drupal\menu\MenuListController';
-  $entity_info['menu']['uri_callback'] = 'menu_uri';
-  $entity_info['menu']['controllers']['form'] = array(
+  $entity_info['menu']->setController('list', 'Drupal\menu\MenuListController');
+  $entity_info['menu']->setUriCallback('menu_uri');
+  $entity_info['menu']->setController('form', array(
     'default' => 'Drupal\menu\MenuFormController',
-  );
+  ));
 }
 
 /**
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
index 6a3eddb..1167d6b 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Database\Connection;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -56,7 +57,7 @@ class MenuLinkStorageController extends DatabaseStorageController {
    * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
    *   The route provider service.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database, RouteProviderInterface $route_provider) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database, RouteProviderInterface $route_provider) {
     parent::__construct($entity_type, $entity_info, $database);
 
     $this->routeProvider = $route_provider;
@@ -69,7 +70,7 @@ public function __construct($entity_type, array $entity_info, Connection $databa
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/menu_link/menu_link.entity.yml b/core/modules/menu_link/menu_link.entity.yml
new file mode 100644
index 0000000..c8c37bb
--- /dev/null
+++ b/core/modules/menu_link/menu_link.entity.yml
@@ -0,0 +1 @@
+menu_link: Drupal\menu_link\Plugin\Core\Entity\MenuLink
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index db3ba75..aac1e85 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -103,7 +103,7 @@ protected function invokeHook($hook, EntityInterface $node) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($node);
     }
 
diff --git a/core/modules/node/node.entity.yml b/core/modules/node/node.entity.yml
new file mode 100644
index 0000000..94b4ab6
--- /dev/null
+++ b/core/modules/node/node.entity.yml
@@ -0,0 +1 @@
+node: Drupal\node\Plugin\Core\Entity\Node
diff --git a/core/modules/shortcut/shortcut.entity.yml b/core/modules/shortcut/shortcut.entity.yml
new file mode 100644
index 0000000..5d9bb9b
--- /dev/null
+++ b/core/modules/shortcut/shortcut.entity.yml
@@ -0,0 +1 @@
+shortcut: Drupal\shortcut\Plugin\Core\Entity\ShortCut
diff --git a/core/modules/system/lib/Drupal/system/Plugin/views/row/EntityRow.php b/core/modules/system/lib/Drupal/system/Plugin/views/row/EntityRow.php
index 0a5ef91..b227c00 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/views/row/EntityRow.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/views/row/EntityRow.php
@@ -59,8 +59,8 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
 
     $this->entityType = $this->definition['entity_type'];
     $this->entityInfo = entity_get_info($this->entityType);
-    $this->base_table = $this->entityInfo['base_table'];
-    $this->base_field = $this->entityInfo['entity_keys']['id'];
+    $this->base_table = $this->entityInfo->getBaseTable();
+    $this->base_field = $this->entityInfo->getKey('id');
   }
 
   /**
diff --git a/core/modules/system/system.entity.yml b/core/modules/system/system.entity.yml
new file mode 100644
index 0000000..462c85b
--- /dev/null
+++ b/core/modules/system/system.entity.yml
@@ -0,0 +1 @@
+menu: Drupal\system\Plugin\Core\Entity\Menu
diff --git a/core/modules/taxonomy/taxonomy.entity.yml b/core/modules/taxonomy/taxonomy.entity.yml
new file mode 100644
index 0000000..5068432
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.entity.yml
@@ -0,0 +1 @@
+taxonomy_vocabulary: Drupal\taxonomy\Plugin\Core\Entity\Vocabulary
diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc
index 679deb5..9c18d16 100644
--- a/core/modules/taxonomy/taxonomy.views.inc
+++ b/core/modules/taxonomy/taxonomy.views.inc
@@ -369,7 +369,7 @@ function taxonomy_field_views_data_views_data_alter(&$data, $field) {
     $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
 
     list($label, $all_labels) = field_views_field_label($field['field_name']);
-    $entity = $entity_info['label'];
+    $entity = $entity_info->getLabel();
     if ($entity == t('Node')) {
       $entity = t('Content');
     }
@@ -381,8 +381,8 @@ function taxonomy_field_views_data_views_data_alter(&$data, $field) {
       'field_name' => $field['field_name'],
       'field table' => _field_sql_storage_tablename($field),
       'field field' => $field['field_name'] . '_tid',
-      'base' => $entity_info['base_table'],
-      'base field' => $entity_info['entity_keys']['id'],
+      'base' => $entity_info->getBaseTable(),
+      'base field' => $entity_info->getKey('id'),
       'label' => t('!field_name', array('!field_name' => $field['field_name'])),
       'join_extra' => array(
         0 => array(
diff --git a/core/modules/tour/tour.entity.yml b/core/modules/tour/tour.entity.yml
new file mode 100644
index 0000000..372f76e
--- /dev/null
+++ b/core/modules/tour/tour.entity.yml
@@ -0,0 +1 @@
+tour: Drupal\tour\Plugin\Core\Entity\Tour
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index e282226..a64c618 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -8,6 +8,7 @@
 namespace Drupal\user;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Password\PasswordInterface;
@@ -61,7 +62,7 @@ public function __construct($entity_type, $entity_info, Connection $database, Pa
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/user/user.entity.yml b/core/modules/user/user.entity.yml
new file mode 100644
index 0000000..02ba54f
--- /dev/null
+++ b/core/modules/user/user.entity.yml
@@ -0,0 +1 @@
+user: Drupal\user\Plugin\Core\Entity\User
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
index 3f7c8bd..a658db1 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
@@ -1330,7 +1330,7 @@ public function query($get_count = FALSE) {
 
       foreach ($entity_tables as $table_alias => $table) {
         $info = entity_get_info($table['entity_type']);
-        $base_field = empty($table['revision']) ? $info['entity_keys']['id'] : $info['entity_keys']['revision'];
+        $base_field = empty($table['revision']) ? $info->getKey('id') : $info->getKey('revision');
         $this->add_field($table_alias, $base_field, '', $params);
       }
     }
@@ -1566,7 +1566,7 @@ function get_entity_tables() {
     // Determine which of the tables are revision tables.
     foreach ($entity_tables as $table_alias => $table) {
       $info = entity_get_info($table['entity_type']);
-      if (isset($info['revision table']) && $info['revision table'] == $table['base']) {
+      if ($info->hasRevisionTable() && $info->getRevisionTable() == $table['base']) {
         $entity_tables[$table_alias]['revision'] = TRUE;
       }
     }
@@ -1599,7 +1599,7 @@ function load_entities(&$results) {
     foreach ($entity_tables as $table_alias => $table) {
       $entity_type = $table['entity_type'];
       $info = entity_get_info($entity_type);
-      $id_key = empty($table['revision']) ? $info['entity_keys']['id'] : $info['entity_keys']['revision'];
+      $id_key = empty($table['revision']) ? $info->getKey('id') : $info->getKey('revision');
       $id_alias = $this->get_field_alias($table_alias, $id_key);
 
       foreach ($results as $index => $result) {
diff --git a/core/modules/views/views.entity.yml b/core/modules/views/views.entity.yml
new file mode 100644
index 0000000..ca3f94e
--- /dev/null
+++ b/core/modules/views/views.entity.yml
@@ -0,0 +1 @@
+view: Drupal\views\Plugin\Core\Entity\View
diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
index 99d23c1..d1ab497 100644
--- a/core/modules/views/views.views.inc
+++ b/core/modules/views/views.views.inc
@@ -109,8 +109,8 @@ function views_views_data() {
   // Registers an entity area handler per entity type.
   foreach (entity_get_info() as $entity_type => $entity_info) {
     // Exclude entity types, which cannot be rendered.
-    if (!empty($entity_info['controllers']['render'])) {
-      $label = $entity_info['label'];
+    if (!$entity_info->hasController('render')) {
+      $label = $entity_info->getLabel();
       $data['views']['entity_' . $entity_type] = array(
         'title' => t('Rendered entity - @label', array('@label' => $label)),
         'help' => t('Displays a rendered @label entity in an area.', array('@label' => $label)),
diff --git a/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php b/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php
new file mode 100644
index 0000000..9c90c5f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\Tests\Core;
+
+use Drupal\Core\AnnotationReader;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Tests\Core\TestAnnotationClass;
+
+class AnnotationReaderTest extends UnitTestCase {
+
+  public function setUp() {
+    $this->reader = new AnnotationReader;
+    $this->reader->setLoader(test_classloader());
+  }
+
+  public function testGetClassAnnotation() {
+    $res = $this->reader->getClassAnnotation('Drupal\Tests\Core\TestAnnotationClass', 'Drupal\Core\Entity\Annotation\EntityType');
+    $expected = new EntityType(array('id' => "test", 'label' => "Test"));
+    $this->assertEquals($expected, $res);
+  }
+
+  public function testGetClassAnnotationCache() {
+    $reader_mock = $this->getMock('Doctrine\Common\Annotations\AnnotationReader');
+    $return_annotation = new EntityType(array('id' => "test", 'label' => "Test"));
+    $reader_mock->expects($this->once())
+                ->method('getClassAnnotations')
+                ->will($this->returnValue($return_annotation));
+    $this->reader->setReader($reader_mock);
+
+    // Expectation is to only call $reader_mock once, so this would fail if the
+    // cache wasn't working correctly.
+    $this->reader->getClassAnnotations('Drupal\Tests\Core\TestAnnotationClass');
+    $this->reader->getClassAnnotations('Drupal\Tests\Core\TestAnnotationClass');
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/TestAnnotationClass.php b/core/tests/Drupal/Tests/Core/TestAnnotationClass.php
new file mode 100644
index 0000000..f7e4994
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TestAnnotationClass.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\Tests\Core;
+
+use Drupal\Core\Entity\Annotation\EntityType;
+
+/**
+ * Defines the comment entity class.
+ *
+ * @EntityType(
+ *   id = "test",
+ *   label = "Test"
+ * )
+ */
+class TestAnnotationClass {
+}
+
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index 08d8871..95ba988 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -1,22 +1,26 @@
 <?php
 
-// Register the namespaces we'll need to autoload from.
-$loader = require __DIR__ . "/../vendor/autoload.php";
-$loader->add('Drupal\\', __DIR__);
-$loader->add('Drupal\Core', __DIR__ . "/../../core/lib");
-$loader->add('Drupal\Component', __DIR__ . "/../../core/lib");
+function test_classloader() {
+  // Register the namespaces we'll need to autoload from.
+  $loader = require __DIR__ . "/../vendor/autoload.php";
+  $loader->add('Drupal\\', __DIR__);
+  $loader->add('Drupal\Core', __DIR__ . "/../../core/lib");
+  $loader->add('Drupal\Component', __DIR__ . "/../../core/lib");
 
-foreach (scandir(__DIR__ . "/../modules") as $module) {
-  $loader->add('Drupal\\' . $module, __DIR__ . "/../modules/" . $module . "/lib");
-  // Add test module classes.
-  $test_modules_dir = __DIR__ . "/../modules/$module/tests/modules";
-  if (is_dir($test_modules_dir)) {
-    foreach (scandir($test_modules_dir) as $test_module) {
-      $loader->add('Drupal\\' . $test_module, $test_modules_dir . '/' . $test_module . '/lib');
+  foreach (scandir(__DIR__ . "/../modules") as $module) {
+    $loader->add('Drupal\\' . $module, __DIR__ . "/../modules/" . $module . "/lib");
+    // Add test module classes.
+    $test_modules_dir = __DIR__ . "/../modules/$module/tests/modules";
+    if (is_dir($test_modules_dir)) {
+      foreach (scandir($test_modules_dir) as $test_module) {
+        $loader->add('Drupal\\' . $test_module, $test_modules_dir . '/' . $test_module . '/lib');
+      }
     }
   }
+  return $loader;
 }
 
+test_classloader();
 require __DIR__ . "/../../core/lib/Drupal.php";
 // Look into removing this later.
 define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
