• Stars
    star
    166
  • Rank 227,104 (Top 5 %)
  • Language
    PHP
  • Created about 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An ORM for "ORMless" persistance of DDD-inspired domain models

Build Status Code Coverage

About TalisORM

A good design starts with some limitations. You can start simple and keep building until you have a large ORM like Doctrine. Or you can choose not to support a mapping configuration, table inheritance, combined write/read models, navigable object graphs, lazy-loading, etc. That's what I'm looking for with TalisOrm. The rules are:

  • You model a persistable domain object as an Aggregate: one (root) Entity, and optionally some Child entities.
  • The child entities themselves have no children.
  • You use the ORM for your write model only. That is, you don't need to fetch hundreds of these aggregates to show them to the user.
  • Your aggregate internally records domain events, which will automatically be released and dispatched after saving changes to the aggregate.

Furthermore:

  • You're going to write your own mapping code, which converts your values or Value objects to and from column values.

I explain more about the motivation for doing this in "ORMless; a Memento-like pattern for object persistence".

You can find some examples of how to use this library in test/TalisOrm/AggregateRepositoryTest/.

Recording and dispatching domain events

A domain event is a simple object indicating that something has happened inside an aggregate (usually this just means that something has changed). You can use the EventRecordingCapabilities trait to save yourself from rewriting a couple of simple lines over and over again.

Immediately after saving an aggregate, the AggregateRepository will call the aggregate's releaseEvents() method, which returns previously recorded domain events. It dispatches these events to an object that implements EventDispatcher. As a user of this library you have to provide your own implementation of this interface, which is very simple. Maybe you just want to forward the call to your favorite event dispatcher, or the one that ships with your framework.

Managing the database schema

Aggregates can implement SpecifiesSchema and, yes, specify their own schema. This can be useful if you want to use a tool to synchronize your current database schema with the schema that your aggregates expect, e.g. the Doctrine DBAL's own 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());

You could also use Doctrine Migrations to automatically generate migrations based on schema changes. It may need a bit of setup, but once you have it working, you'll notice that this tool needs a SchemaProviderInterface instance (note: this interface is only available in recent versions of doctrine/migrations, which requires PHP 7). You can easily set up an adapter for AggregateSchemaProvider. For example:

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();
    }
}

Protecting against concurrent updates

Traditionally, we PHP developers aren't used to protect our aggregates against concurrent updates. Concurrent updates after all are a matter of chance. Maybe there aren't that many users who are working on the same aggregate in your project. But if you're worried that it might happen, there's an easy solution built-in to TalisORM: optimistic concurrency locking.

You need to take the following steps to make it work:

Make sure the table definition for your aggregate has an Aggregate::VERSION_COLUMN column, and that your fromState() and state() methods are aware of it. For example:

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');
    }
}

The above setup will protect your aggregate against concurrent updates between retrieving the aggregate from the database and saving it again. However, you may want to warn a user who's working with the aggregate's data in the user interface that once they store the object, someone else has modified it. To do this, you need to remember the version of the aggregate the user is looking at in the user's session. An outline of this solution:

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);

More Repositories

1

symfony-console-form

Use Symfony forms for Console command input
PHP
365
star
2

random-disaster-bundle

ProjectBundle a.k.a. RandomDisasterBundle
PHP
115
star
3

live-code-coverage

Generate code coverage reports on a live server
PHP
113
star
4

symfony-service-definition-validator

A set of tools for validating Symfony service definitions.
PHP
89
star
5

convenient-immutability

Make objects initially inconsistent, yet eventually immutable
PHP
78
star
6

php-ast-inspector

Kind of a step-debugger for your Abstract Syntax Tree
PHP
62
star
7

symfony-bundle-plugins

Allow Symfony bundles to have plugins
PHP
57
star
8

naive-serializer

A naive JSON serializer which recursively converts an object graph to and from JSON, without any configuration or custom code.
PHP
52
star
9

badges

Package which will receive many nice badges
PHP
49
star
10

layers-ports-and-adapters-workshop

Sandbox project for the "Advanced Web Application Architecture" training.
PHP
47
star
11

building-autonomous-services-workshop

Code and assignments for the "Building Autonomous Services" workshop module.
PHP
42
star
12

microsoft-translator

PHP library for making calls to the Microsoft Translator V3 API
PHP
39
star
13

phpunit-asynchronicity

Library for asserting things that happen asynchronously with PHPUnit
PHP
35
star
14

decoupling-from-infrastructure-workshop

Sandbox project for the "Decoupling from infrastructure" workshop
PHP
35
star
15

broadway-serialization

Serialization helpers for Broadway
PHP
28
star
16

hexagonal-architecture-workshop

PHP
28
star
17

php-workshop-tools

A set of useful utilities for PHP workshop code bases that I would otherwise write again and again.
PHP
25
star
18

behat-local-code-coverage-extension

Behat extension for generating (local) code coverage data.
PHP
24
star
19

high-quality-bundles-project

The "Symfony Standard Edition" adapted for my High Quality Bundles workshop
PHP
23
star
20

behat-expect-exception

The missing Behat feature: expecting exceptions.
PHP
22
star
21

simple-bus

Warning: this project has been moved to its own organization and the package has been split. Please take a look at its new location:
PHP
22
star
22

leanpub-api-client

PHP
22
star
23

recipes-for-decoupling-phpstan-rules

Repository containing all the PHPStan rules from the book "Recipes for Decoupling"
PHP
21
star
24

doctrine-orm-value-object

A library for using value objects inside Doctrine entities
PHP
20
star
25

php-duck-typing

A simple library for duck-typing in PHP 7.
PHP
18
star
26

tactical-ddd-workshop

Code and assignments for the "Tactical DDD" workshop module.
PHP
18
star
27

LazyServicesBundle

PHP
17
star
28

php-for-the-web

The source code for the book "PHP for the Web - Learning PHP without a framework"
PHP
16
star
29

behat-remote-code-coverage-extension

Behat extension for generating (remote) code coverage data.
PHP
13
star
30

php-parser-instantiation-printer

For printing the code needed to instantiate the given PHP-Parser nodes
PHP
13
star
31

aggregate-design-workshop

Sandbox project for my Domain-Driven Aggregate Design workshop
PHP
12
star
32

domstreamwrapper

A PHP stream wrapper for modifying DOM node values using traditional file manipulation functions
PHP
10
star
33

cqrs-and-event-sourcing-workshop

Code and assignments for the "CQRS & Event Sourcing" workshop module.
PHP
10
star
34

code-sniffer-sniff-dsl

DSL for writing sniffs for PHP_CodeSniffer
PHP
10
star
35

ConsoleCommandGeneratorBundle

Bundle for generating Symfony2 commands
PHP
9
star
36

nginx-multi-host-docker

Docker Compose setup for multi-hostname Let's Encrypt-secured websites.
Shell
9
star
37

docker-swarm-workshop

An introduction to Docker Swarm
Shell
9
star
38

leanpub-sampler

A simple script for generating samples for Leanpub books
PHP
9
star
39

practicing-domain-driven-entity-and-value-object-design

Sandbox project for the "Domain-Driven Entity and Value Object Design" training.
PHP
8
star
40

workshop-unit-testing

Accompanying project for my PHPUnit workshop
PHP
8
star
41

phpstan-twig-analysis

PHP
7
star
42

docker-compose-workshop

An introduction to Docker Compose
PHP
7
star
43

symfony-controller-annotation

Helper library for creating event listeners that influence a Symfony application flow based on annotations
PHP
7
star
44

docker-workshop

Getting started with Docker
PHP
7
star
45

php-coding-dojo

Empty project which can be used in a PHP and PHPUnit-oriented coding dojo
PHP
7
star
46

service-integration-workshop

Code and assignments for the "Integration Bounded Contexts" workshop module.
PHP
6
star
47

advanced-testing-workshop

Code and assignments for the "Advanced Testing" workshop module.
PHP
6
star
48

testing-playground

Playground for (unit/integration/acceptance) testing-oriented workshops.
PHP
6
star
49

living-documentation

A framework for living documentation for PHP projects (very much a work in progress).
PHP
5
star
50

modeling-exercises

Modeling exercises
PHP
4
star
51

symfony-decoupling-workshop

Sandbox project for the Symfony decoupling workshop
PHP
4
star
52

tail-event-stream

A simple, tail-based event stream
PHP
4
star
53

symfony-console-integration-test

This project is a demo of an integration test for a Symfony console command
PHP
4
star
54

phpstan-workshop

Sandbox project for the PHPStan workshop
PHP
4
star
55

MicrosoftTranslatorBundle

Symfony2 bundle for the microsoft-translator PHP library
PHP
4
star
56

doctrine-dbal-test-service-provider

Service provider for tests in need of a Doctrine DBAL connection
PHP
3
star
57

gherkin-features-html-exporter

An exporter for Gherkin feature files to HTML, written in PHP
PHP
3
star
58

test-first-application-development-workshop

Sandbox project for the "Test-Driven Application Development" training.
PHP
3
star
59

make-workshop

Assignments and source files for the Make workshop
Dockerfile
2
star
60

amqp-integration-patterns

For now: a playground
PHP
2
star
61

static-analysis-and-automated-refactoring-workshop

Sandbox environment for the "Static Analysis and Automated Refactoring" workshop
PHP
2
star
62

the-page-class

An exercise in refactoring legacy code
PHP
2
star
63

symfony-dependency-injection-workshop

Sandbox project for a Symfony Dependency Injection workshop
PHP
2
star
64

phpunit-test-service-container

Simple service container for PHPUnit tests
PHP
2
star
65

mail-comments-common

PHP
2
star
66

RomanNumeralsKataJava

Java
1
star
67

MicrosoftTranslatorServiceProvider

Silex service provider for using the Microsoft Translator V2 API
PHP
1
star
68

churn-php-visualization

HTML
1
star
69

MarsRoverKata

Mars rover kata in Java for the Java Beginners Meetup.
Java
1
star
70

moving-forward-with-legacy-code-workshop

Sandbox project accompanying the "Moving Forward With Legacy Code" workshop
PHP
1
star
71

doctrine-orm-value-object-bundle

PHP
1
star
72

decoupling-workshop

Sandbox project for the Principles of Decoupling workshop
PHP
1
star
73

doctrine-orm-test-service-provider

Service provider for tests in need of a Doctrine entity manager
PHP
1
star