Skip to content

Commit

Permalink
Deep related projection update support
Browse files Browse the repository at this point in the history
  • Loading branch information
MrHash committed May 13, 2016
1 parent 8a561c9 commit e831895
Show file tree
Hide file tree
Showing 34 changed files with 744 additions and 159 deletions.
158 changes: 79 additions & 79 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/Infrastructure/DataAccess/Query/AttributeCriteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Honeybee\Infrastructure\DataAccess\Query;

class AttributeCriteria implements CriteriaInterface
use Trellis\Common\Object;

class AttributeCriteria extends Object implements CriteriaInterface
{
protected $attribute_path;

Expand Down
4 changes: 3 additions & 1 deletion src/Infrastructure/DataAccess/Query/Comparison.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Honeybee\Infrastructure\DataAccess\Query;

class Comparison
use Trellis\Common\Object;

class Comparison extends Object
{
const EQUALS = 'eq';

Expand Down
4 changes: 3 additions & 1 deletion src/Infrastructure/DataAccess/Query/SearchCriteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Honeybee\Infrastructure\DataAccess\Query;

class SearchCriteria implements CriteriaInterface
use Trellis\Common\Object;

class SearchCriteria extends Object implements CriteriaInterface
{
protected $attribute_path;

Expand Down
4 changes: 3 additions & 1 deletion src/Infrastructure/DataAccess/Query/SortCriteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Honeybee\Infrastructure\DataAccess\Query;

class SortCriteria implements CriteriaInterface
use Trellis\Common\Object;

class SortCriteria extends Object implements CriteriaInterface
{
const SORT_ASC = 'asc';

Expand Down
3 changes: 2 additions & 1 deletion src/Infrastructure/DataAccess/Query/SpatialCriteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace Honeybee\Infrastructure\DataAccess\Query;

use Trellis\Common\Object;
use Honeybee\Infrastructure\DataAccess\Query\Geometry\PositionInterface;

class SpatialCriteria implements CriteriaInterface
class SpatialCriteria extends Object implements CriteriaInterface
{
protected $attribute_path;

Expand Down
1 change: 1 addition & 0 deletions src/Projection/EventHandler/ProjectionUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ protected function onAggregateRootNodeMoved(AggregateRootNodeMovedEvent $event)

$child_path_parts = [ $child_projection->getMaterializedPath(), $child_projection->getIdentifier() ];
$recursive_children_result = $this->getQueryService()->find(
// @todo scan and scroll support
new Query(
new CriteriaList,
new CriteriaList(
Expand Down
110 changes: 62 additions & 48 deletions src/Projection/EventHandler/RelationProjectionUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Honeybee\Projection\EventHandler;

use Honeybee\EntityInterface;
use Honeybee\Common\Error\RuntimeError;
use Honeybee\Infrastructure\Config\ConfigInterface;
use Honeybee\Infrastructure\DataAccess\Query\AttributeCriteria;
Expand All @@ -10,32 +11,17 @@
use Honeybee\Infrastructure\DataAccess\Query\QueryServiceMap;
use Honeybee\Infrastructure\DataAccess\Query\Comparison\Equals;
use Honeybee\Infrastructure\DataAccess\Storage\StorageWriterMap;
use Honeybee\Infrastructure\Event\Bus\EventBusInterface;
use Honeybee\Infrastructure\Event\Event;
use Honeybee\Infrastructure\Event\EventHandler;
use Honeybee\Infrastructure\Event\EventInterface;
use Honeybee\Model\Aggregate\AggregateRootTypeMap;
use Honeybee\Model\Event\AggregateRootEventInterface;
use Honeybee\Model\Event\EmbeddedEntityEventInterface;
use Honeybee\Model\Event\EmbeddedEntityEventList;
use Honeybee\Model\Task\CreateAggregateRoot\AggregateRootCreatedEvent;
use Honeybee\Model\Task\ModifyAggregateRoot\AddEmbeddedEntity\EmbeddedEntityAddedEvent;
use Honeybee\Model\Task\ModifyAggregateRoot\AggregateRootModifiedEvent;
use Honeybee\Model\Task\ModifyAggregateRoot\ModifyEmbeddedEntity\EmbeddedEntityModifiedEvent;
use Honeybee\Model\Task\ModifyAggregateRoot\RemoveEmbeddedEntity\EmbeddedEntityRemovedEvent;
use Honeybee\Model\Task\MoveAggregateRootNode\AggregateRootNodeMovedEvent;
use Honeybee\Model\Task\ProceedWorkflow\WorkflowProceededEvent;
use Honeybee\Projection\ProjectionInterface;
use Honeybee\Projection\ProjectionTypeInterface;
use Honeybee\Projection\ProjectionTypeMap;
use Honeybee\Projection\ProjectionUpdatedEvent;
use Psr\Log\LoggerInterface;
use Trellis\Runtime\Attribute\AttributeInterface;
use Trellis\Runtime\Attribute\AttributeValuePath;
use Trellis\Runtime\Attribute\EmbeddedEntityList\EmbeddedEntityListAttribute;
use Trellis\Runtime\Entity\EntityInterface;
use Trellis\Runtime\Entity\EntityList;
use Trellis\Runtime\Entity\EntityReferenceInterface;
use Trellis\Runtime\ReferencedEntityTypeInterface;

class RelationProjectionUpdater extends EventHandler
{
Expand Down Expand Up @@ -65,23 +51,41 @@ public function __construct(

public function handleEvent(EventInterface $event)
{
return $this->updateAffectedRelatives($event);
$updated_projections = $this->updateAffectedRelatives($event);
// @todo post ProjectionUpdatedEvent to the event-bus ('honeybee.projection_events' channel)
return $updated_projections;
}

protected function updateAffectedRelatives(AggregateRootEventInterface $event)
{
$affected_ar_type = $this->aggregate_root_type_map->getByClassName($event->getAggregateRootType());
$foreign_projection_type = $this->projection_type_map->getItem($affected_ar_type->getPrefix());
$foreign_projection_type = $this->getProjectionType($event);
$foreign_projection_type_impl = get_class($foreign_projection_type);

$affected_attributes = array_keys($event->getData());
foreach ($event->getEmbeddedEntityEvents() as $embedded_event) {
$affected_attributes[] = $embedded_event->getParentAttributeName();
}

// build a list of referenced entity list attributes which are affected by this event
$referenced_attributes = $this->getRelationProjectionType()->getReferenceAttributes()->filter(
function (AttributeInterface $attribute) {
$yield = true;
while ($attribute->getParent() || !$attribute->getOption('mirrored', false)) {
if (!$attribute->getOption('mirrored', false)) {
$yield = false;
break;
}
$attribute = $attribute->getParent();
}
return $yield;
}
);

// Determine whether any mirrored attributes exist on these referenced entities and if
// so prepare a query to load any projections with matching relations for update
$attributes_to_update = [];
$reference_filter_list = new CriteriaList([], CriteriaList::OP_OR);
foreach ($this->getProjectionType()->getReferenceAttributes() as $attribute_path => $ref_attribute) {
foreach ($referenced_attributes as $attribute_path => $ref_attribute) {
$mirror_attributes = [];
foreach ($ref_attribute->getEmbeddedEntityTypeMap() as $reference_embed) {
$referenced_type_impl = ltrim($reference_embed->getReferencedTypeClass(), '\\');
Expand All @@ -97,9 +101,10 @@ function ($attribute) use ($affected_attributes) {
}
}
}

// Add to the filter to load projections where mirrored attributes need to be updated
if (!empty($mirror_attributes)) {
$attributes_to_update[$attribute_path] = $mirror_attributes;
$filter_field_path = sprintf('%s.referenced_identifier', $attribute_path);
$reference_filter_list->push(
new AttributeCriteria(
$this->buildFieldFilterSpec($ref_attribute),
Expand All @@ -109,7 +114,8 @@ function ($attribute) use ($affected_attributes) {
}
}

$updated_entities = new EntityList;
// finalize query and get results from the query service
$updated_projections = new EntityList;
if (!empty($reference_filter_list)) {
$filter_criteria_list = new CriteriaList;
$filter_criteria_list->push(
Expand All @@ -120,39 +126,36 @@ function ($attribute) use ($affected_attributes) {
} else {
$filter_criteria_list->push($reference_filter_list);
}
// @todo scan and scroll support
$query_result = $this->getQueryService()->find(
new Query(
new CriteriaList,
$filter_criteria_list,
new CriteriaList,
0,
100
10000
)
);
$entities_to_update = $query_result->getResults();

foreach ($entities_to_update as $entity_to_update) {
foreach ($attributes_to_update as $attribute_path => $mirror_attributes) {
$reference_embeds = AttributeValuePath::getAttributeValueByPath($entity_to_update, $attribute_path);
foreach ($reference_embeds as $pos => $reference_embed) {
if ($reference_embed->getReferencedIdentifier() === $event->getAggregateRootIdentifier()) {
$reference_embeds->removeItem($reference_embed);
$reference_embeds->insertAt(
$pos,
$reference_embed->getType()->createEntity(
array_merge($reference_embed->toNative(), $event->getData()),
$reference_embed->getParent()
)
);
}
}

// iterate the related projections from results and merge changes from event data
$related_projections = new EntityList($query_result->getResults());
$updated_projections->append($related_projections->withUpdatedEntities(
$event->getData(),
function (EntityInterface $entity) use ($event) {
return $entity instanceof EntityReferenceInterface
&& $entity->getReferencedIdentifier() === $event->getAggregateRootIdentifier();
}
$this->getStorageWriter()->write($entity_to_update);
$updated_entities->push($entity_to_update);
}
));
}

// write updated projections to storage
// @todo introduce a writeMany method to the storageWriter to save requests
foreach ($updated_projections as $projection) {
$this->getStorageWriter()->write($projection);
}

return $updated_entities;
// drop the mic
return $updated_projections;
}

protected function buildFieldFilterSpec(EmbeddedEntityListAttribute $embed_attribute)
Expand All @@ -169,9 +172,21 @@ protected function buildFieldFilterSpec(EmbeddedEntityListAttribute $embed_attri
return implode('.', $filter_parts);
}

protected function getProjectionType()
protected function getProjectionType(AggregateRootEventInterface $event)
{
$ar_type = $this->aggregate_root_type_map->getByClassName($event->getAggregateRootType());

if (!$this->projection_type_map->hasKey($ar_type->getPrefix())) {
throw new RuntimeError('Unable to resolve projection type for prefix: ' . $ar_type->getPrefix());
}

return $this->projection_type_map->getItem($ar_type->getPrefix());
}

protected function getRelationProjectionType()
{
$projection_type_prefix = $this->config->get('projection_type');
// @todo should the projection type map throw a runtime error internally on getItem?
if (!$this->projection_type_map->hasKey($projection_type_prefix)) {
throw new RuntimeError('Unable to resolve projection-type for prefix: ' . $projection_type_prefix);
}
Expand All @@ -183,7 +198,7 @@ protected function getQueryService()
{
$query_service_default = sprintf(
'%s::query_service',
$this->getProjectionType()->getPrefix()
$this->getRelationProjectionType()->getPrefix()
);

$query_service_key = $this->config->get('query_service', $query_service_default);
Expand All @@ -196,10 +211,9 @@ protected function getQueryService()

protected function getStorageWriter()
{
$projection_type = $this->getProjectionType();
$storage_writer_default = sprintf(
'%s::projection.standard::view_store::writer',
$this->getProjectionType()->getPrefix()
$this->getRelationProjectionType()->getPrefix()
);

$storage_writer_key = $this->config->get('storage_writer', $storage_writer_default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
parent::__construct(
'Player',
[
new Text('name', $this, []),
new Text('name', $this, [], $parent_attribute),
new EmbeddedEntityListAttribute(
'profiles',
$this,
[
'entity_types' => [
EntityType::NAMESPACE_PREFIX . 'Game\\Embed\\ProfileType',
]
]
],
$parent_attribute
),
new EmbeddedEntityListAttribute(
'unmirrored_profiles',
Expand All @@ -34,7 +35,8 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
'entity_types' => [
EntityType::NAMESPACE_PREFIX . 'Game\\Embed\\ProfileType',
]
]
],
$parent_attribute
),
],
new Options(
Expand Down
13 changes: 13 additions & 0 deletions testing/unit/Projection/EventHandler/Fixtures/Model/Team/Team.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Honeybee\Tests\Projection\EventHandler\Fixtures\Model\Team;

use Honeybee\Model\Aggregate\AggregateRoot;

class Team extends AggregateRoot
{
public function getName()
{
return $this->getValue('name');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Honeybee\Tests\Projection\EventHandler\Fixtures\Model\Team;

use Trellis\Common\Options;
use Trellis\Runtime\Attribute\Text\TextAttribute as Text;
use Honeybee\Tests\Projection\EventHandler\Fixtures\Model\EntityType;
use Workflux\StateMachine\StateMachineInterface;

class TeamType extends EntityType
{
public function __construct(StateMachineInterface $state_machine)
{
parent::__construct('Team', $state_machine);
}

public function getDefaultAttributes()
{
return array_merge(
parent::getDefaultAttributes(),
[
new Text('name', $this, [ 'mandatory' => true ]),
]
);
}

public static function getEntityImplementor()
{
return Team::CLASS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
parent::__construct(
'Badge',
[
new Text('award', $this, [], $parent_attribute),
new Text('award', $this, [ 'mirrored' => true ], $parent_attribute),
],
new Options([]),
$parent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
parent::__construct(
'Challenge',
[
new IntegerAttribute('attempts', $this, [], $parent_attribute),
new IntegerAttribute('attempts', $this, [ 'mirrored' => true ], $parent_attribute),
],
new Options([]),
$parent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
parent::__construct(
'Profile',
[
new Text('alias', $this, [], $parent_attribute),
new TextListAttribute('tags', $this, [], $parent_attribute),
new Text('alias', $this, [ 'mirrored' => true ], $parent_attribute),
new TextListAttribute('tags', $this, [ 'mirrored' => true ], $parent_attribute),
new EntityReferenceListAttribute(
'teams',
$this,
[
'mirrored' => true,
'entity_types' => [
ProjectionType::NAMESPACE_PREFIX . 'Game\\Reference\\TeamType',
]
Expand All @@ -35,6 +36,7 @@ public function __construct(EntityTypeInterface $parent = null, AttributeInterfa
'badges',
$this,
[
'mirrored' => true,
'entity_types' => [
ProjectionType::NAMESPACE_PREFIX . 'Game\\Embed\\BadgeType',
]
Expand Down

0 comments on commit e831895

Please sign in to comment.