Why are values in Entites obtained directly and sometimes by using getters?
Ok, trying to answer this. This could get a bit long :)

First, to have that out of the way, Drupal\Core\Entity\Entity::id() is not TypedData relevant, only content entities (subclasses for ContentEntityBase) are typed data at the moment. So that implementation is for example used for config entities.

Now about the different methods that you mentioned.


getValue() is part of TypedDataInterface and it's not strictly defined what it returns, but it can be understood as the raw value of that typed data object. For anything that is not a so called primitive value (like a string or integer), getValue() returns an array. So if you call getValue() on the first field item of the node body ($node-&gt;body[0]), it will return array('value' =&gt; ..., 'format' =&gt; 'text_plain'). If you call it on the field item list, you get array(0 =&gt; array('value' =&gt; ..., 'format' =&gt; 'text_plain')). So for all complex data (another typed data term, it defines an objects that consists of sub-properties, like an entity or field item), think of getValue() as toArray().
Content entities no longer have public class properties. $entity-&gt;id-&gt;value goes through $entity-&gt;__get('id')-&gt;value, which is almost identical to $entity-&gt;get('id')-&gt;value, with the following differences:


get() currently only supports defined fields. _get()/_set() allow to set anything on an entity, mostly due to BC reasons, because a lot of code still sets tons of random stuff on $node. So print $node-&gt;bla works, $node-&gt;get('bla') will throw an InvalidArgumentException('Field bla is unknown.'). This could be changed if we want to.
Before PHP 5.4, $node-&gt;get('body')[1] is not supported (while $node->body1 works fine), so you have to use the rather weird $node-&gt;get('body')-&gt;offsetGet(1) (this is ArrayAccess, #2110467: Add first(), get($index) and possibly other methods to ListInterface will hopefully soon change that to a $node-&gt;get('body')-&gt;get(1)). Now that D8 will require 5.4, this isn't much of an argument anymore and the referenced issue makes it easier too.
get() is faster than __get(). On the large scale, there's not a big difference I think (other things are worse), but relatively speaking are magic method calls considerably slower than non-magic ones.

The ->value part involves more magic methods. To explain that, I first have to mention that all entity fields (including base fields like the ID) now have a fixed structure enforced: Entity : Field item list : Field item : Field properties (like value, format, target_id). If you write $entity-&gt;id-&gt;value then ContentEntityBase::__get() gives you the field item list for the id field. Then you call $field_item_list->value, which points to FieldItemList::__get() which assumes that you want to access the first field item, so it passes the call through to FieldItemBase::__get(), which then returns the value. I suggest that's enough detail for that question, if you want to know more about FieldItemBase, I'm happy to answer that in a separate question.
Now, $entity-&gt;id-&gt;getValue() and $entity-&gt;id-&gt;value are often mixed up, but they are not actually related in any way, it's just an unfortunate naming conflict. value happens to be the pseudo-standard main field property name, especially for those that only have a single value. Reference fields use target_id, and if you want to name the property of your own field type banana , then all you have to do is set it up accordingly in your FieldTypeItem::getPropertyDefinitions() implementation and it will be $entity-&gt;your_field-&gt;banana.
There are (at least) two remaining issues that will affect these API's. That is #2002134: Move TypedData metadata introspection from data objects to definition objects and #2002138: Use adapters for supporting typed data. The main goal of those combined is that entities and field items are no longer typed data objects themself but can be used as typed data through adapter classes. That in turn means that those classes do not have to respect (and poorly implement in case of entities) the TypedDataInterface/ComplexDataInterface interfaces but can define method names that make more sense in their context. For example, we can hopefully rename getValue() to toArray() or something else that has no naming conflict with value, we can rename $entity->getPropertyDefinitions() to $entity->getFieldDefinitions() and so on.


So, to come back to what your actual (I think) question is, $entity->id->value and $entity->get('id)->value are almost identical, both are currently supported, but I usually prefer and recommend get(). __get() could still change (although I consider it unlikely, as nobody is pushing there), so if you use get(), you're on the safe side I think.