Skip to content

Instantly share code, notes, and snippets.

@timoschinkel
Last active April 29, 2020 15:23
Show Gist options
  • Save timoschinkel/a017a6fe5fc668f093874496b11ea505 to your computer and use it in GitHub Desktop.
Save timoschinkel/a017a6fe5fc668f093874496b11ea505 to your computer and use it in GitHub Desktop.
Writing
<?php
// In the current solution we have three separate interfaces. This allows for a clean separation of functionality:
interface QueryExecutorInterface
{
public function execute(QueryInterface $query): array;
}
interface QueryMetadataServiceInterface
{
public function getLatestQueryMetaData(): QueryMetadata;
}
interface QueryTransactionServiceInterface
{
public function startTransaction(): QueryTransaction;
}
// But it also provides us with a side effect when you want to perform actions in a transaction or when you want to
// retrieve the last insert identifier. This is caused by the splitting of interfaces. Our writer implements all
// three interface, but due to type hinting we need to inject that same writer three times. We rely on our configuration
// to ensure that the queries executed within a transaction are actually performed on the same connection as this is not
// enforced. Something similar is the case when fetching the meta service from the last query. Given the signature it can
// be that all three services are communicating to a different database.
class MyRepository
{
/** @var QueryExecutorInterface */
private $writer;
/** @var QueryTransactionServiceInterface */
private $transactionService;
/** @var QueryMetadataServiceInterface */
private $metadataService;
public function __construct(
QueryExecutorInterface $writer,
QueryTransactionServiceInterface $transactionService,
QueryMetadataServiceInterface $metadataService
) {
$this->writer = $writer;
$this->transactionService = $transactionService;
$this->metadataService = $metadataService;
}
public function action()
{
$transaction = $this->transactionService->startTransaction();
$writer->execute(new MyQuery(/* ... */));
$metadata = $metadataService->getLatestQueryMetaData();
// ...
$transaction->commit();
}
}
<?php
// One option to ensure that the meta service and queries are tied to the same connection as the transaction
// is to make the transaction more than a value object:
interface QueryExecutorInterface
{
public function execute(QueryInterface $query): array;
}
interface QueryMetadataServiceInterface
{
public function getLatestQueryMetaData(): QueryMetadata;
}
interface QueryTransactionServiceInterface
{
public function startTransaction(): QueryTransactionInterface;
}
interface QueryTransactionInterface extends QueryMetadataServiceInterface, QueryExecutorInterface
{
public function commit(): void;
public function rollback(): void;
}
// Now everything related to the transaction is actually called on the transaction object itself. This
// reduces the amount of dependencies injected into a repository and will ensure that the queries and
// meta service are retrieved on the same connection via code. Downside of this is that this solution
// is not solving the double dependencies when the need is there for meta service, but not for a transaction.
class MyRepository
{
/** @var QueryTransactionServiceInterface */
private $transactionService;
public function __construct(QueryTransactionServiceInterface $transactionService)
{
$this->transactionService = $transactionService;
}
public function action()
{
$transaction = $this->transactionService->startTransaction();
$transaction->execute(new MyQuery(/* ... */));
$metadata = $transaction->getLatestQueryMetaData();
// ...
$transaction->commit();
}
}
<?php
// Another solution would be to introduce a new interface that combines the three interface. This
// is actually what we implicitly do right now; `MysqlQueryExecutor` already implements all three
// interfaces.
interface QueryExecutorInterface
{
public function execute(QueryInterface $query): array;
}
interface QueryMetadataServiceInterface
{
public function getLatestQueryMetaData(): QueryMetadata;
}
interface QueryTransactionServiceInterface
{
public function startTransaction(): QueryTransaction;
}
interface WriteQueryExecutorInterface extends QueryExecutorInterface, QueryMetadataServiceInterface, QueryTransactionServiceInterface
{}
// This way we reduce the dependencies again to one. But instead of moving the logic to the transaction centralization
// is ensured by the new interface.
class MyRepository
{
/** @var WriteQueryExecutorInterface */
private $writer;
public function __construct(WriteQueryExecutorInterface $writer)
{
$this->writer = $writer;
}
public function action()
{
$transaction = $$writer->startTransaction();
$writer->execute(new MyQuery(/* ... */));
$metadata = $writer->getLatestQueryMetaData();
// ...
$transaction->commit();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment