matthiasnoback/talis-orm

v0.2.1 2020-12-13 12:42 UTC

README

Build Status Code Coverage

关于 TalisORM

良好的设计始于一些限制。你可以从一个简单的开始,一直构建到一个大的ORM如Doctrine。或者你可以选择不支持映射配置、表继承、组合读写模型、可导航的对象图、懒加载等。这正是TalisOrm所追求的。规则如下

  • 你将可持久化领域对象建模为一个 聚合:一个(根)实体,以及可选的一些 子实体
  • 子实体本身没有子实体。
  • 你仅使用ORM进行你的 写模型。也就是说,你不需要获取成百上千个这样的聚合来展示给用户。
  • 你的聚合内部记录领域事件,这些事件将在保存聚合更改后自动释放和分发。

此外

  • 你需要编写自己的映射代码,将你的值或 值对象 转换为和从列值。

我在"ORMless; a Memento-like pattern for object persistence"中解释了这样做的原因。

你可以在test/TalisOrm/AggregateRepositoryTest/中找到如何使用此库的一些示例。

记录和分发领域事件

领域事件是一个简单的对象,指示聚合内部发生了某些事情(通常这意味着发生了某些变化)。你可以使用EventRecordingCapabilities特性来避免重复编写几个简单的行。

在保存聚合后,AggregateRepository将调用聚合的releaseEvents()方法,该方法返回先前记录的领域事件。它将这些事件分发给实现EventDispatcher的对象。作为此库的用户,你必须提供自己的实现,这非常简单。也许你只想将调用转发到你的首选事件分发器,或者框架自带的事件分发器。

管理数据库模式

聚合可以实现SpecifiesSchema并,是的,指定自己的模式。如果你想要使用工具来同步当前数据库模式与聚合期望的模式,这可能很有用,例如Doctrine DBAL的自己的SingleDatabaseSynchronizer

use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
use TalisOrm\Schema\AggregateSchemaProvider;

// set up or reuse a Doctrine\DBAL\Connection instance
$connection = ...;

$schemaProvider = new AggregateSchemaProvider($connection, [
    // list all the aggregate class names of your application, e.g.
    User::class,
    Order::class
]);
$synchronizer = new SingleDatabaseSynchronizer($connection);
$synchronizer->createSchema($schemaProvider->createSchema());

你也可以使用Doctrine Migrations来自动根据模式更改生成迁移。这可能需要一些设置,但一旦开始工作,你会发现这个工具需要一个SchemaProviderInterface实例(注意:此接口仅在doctrine/migrations的较新版本中可用,这需要PHP 7)。你可以轻松设置AggregateSchemaProvider的适配器。例如

final class AggregateMigrationsSchemaProvider implements SchemaProviderInterface
{
    /**
     * @var AggregateSchemaProvider
     */
    private $aggregateSchemaProvider;

    public function __construct(AggregateSchemaProvider $aggregateSchemaProvider)
    {
        $this->aggregateSchemaProvider = $aggregateSchemaProvider;
    }

    public function createSchema(): Schema
    {
        return $this->aggregateSchemaProvider->createSchema();
    }
}

防止并发更新

传统上,PHP开发者不习惯保护我们的聚合免受并发更新的影响。毕竟,并发更新是一件偶然的事情。也许在你的项目中没有那么多用户正在处理相同的聚合。但如果你担心这种情况可能发生,TalisORM内置了一个简单的解决方案:乐观并发锁定。

你需要采取以下步骤来使其生效

确保您聚合的表定义中包含一个Aggregate::VERSION_COLUMN列,并且您的fromState()state()方法都了解它。例如

final class Order implements Aggregate, SpecifiesSchema
{
    /**
     * @var int
     */
    private $aggregateVersion;

    public function state(): array
    {
        // N.B. It's important to increment the version manually every time state() gets called!
        $this->aggregateVersion++;

        return [
            // ...
            Aggregate::VERSION_COLUMN => $this->aggregateVersion
        ];
    }

    public static function fromState(array $aggregateState, array $childEntityStatesByType): Aggregate
    {
        $order = new self();

        // ...

        $order->aggregateVersion = $aggregateState[Aggregate::VERSION_COLUMN];

        return $order;
    }

    /**
     * Only if your aggregate implements SpecifiesSchema:
     */
    public static function specifySchema(Schema $schema): void
    {
        $table = $schema->createTable('orders');

        // ...

        $table->addColumn(Aggregate::VERSION_COLUMN, 'integer');
    }
}

上述设置将保护您的聚合在从数据库检索聚合和再次保存它之间不受并发更新的影响。然而,您可能希望警告正在用户界面中处理聚合数据的用户,一旦他们存储了对象,其他人可能已经修改了它。为此,您需要在用户的会话中记住用户正在查看的聚合版本。此解决方案的概述

final class Order implements Aggregate, SpecifiesSchema
{
    // ...

    public function setAggregateVersion(int $version): void
    {
        $this->aggregateVersion = $version;
    }

    public function aggregateVersion(): int
    {
        return $this->aggregateVersion;
    }
}

/*
 * Inside the controller which (for instance) renders a form, allowing the
 * user to modify some aspect of the aggregate:
 */
$order = $repository->getById($orderId);
$session->set('aggregate_version', $order->aggregateVersion());
// show form

/*
 * Inside the controller which modifies the aggregate based on the data the
 * user provided:
 */
$order = $repository->getById($orderId);
$order->setAggregateVersion($session->get('aggregate_version');

$order->makeSomeChange();

// This will compare the provided version to the version in the database:
$repository->save($order);