SlideShare a Scribd company logo
1 of 43
Download to read offline
Jak skutecznie read model
Case study
Tomasz Surowiec, Gliwice 28.09.2022
Read Model
Model odczytu wg CQRS
PHP Developer @ TSH
Suro
Set the stage
Aplikacja legacy - krótka
charakterystyka biznesowa
• Operuje na zleceniach wykonania usługi
• Długo obecna na na rynku
• Dochodowa
• Elastyczna
Pola dodatkowe
• Dynamicznie definiowane przez administratora
• Reguły biznesowe
• Filtrowane konfigurowalne listy
CREATE TABLE custom_fields (
assignmentId int NOT NULL,
name varchar(32) NOT NULL,
value varchar(128) NOT NULL,
CONSTRAINT cf_key PRIMARY KEY(assignmentId,name)
)
assignmentId name value
------------ ------------- ------------
123 ACF Index B00001F
123 GGV Reference 543-GGV200-1
123 Inclination +770
EAV Encja-Atrybut-Wartość
IF a.STATUS = ‘PENDING’ AND cf.FOO > cf.BAR SET cf.BAZ = cf.BAZ + 1
Reguły biznesowe czyli tzw workflow
• Składane z klocków metodą drag and drop
• Definiowane dla typu zlecenia
• Mogły pobierać wartości ze zleceń powiązanych
Gridy - dynamicznie definiowane listy zleceń
SELECT *
FROM assignment a
JOIN custom_fields cf ON a.id = cf.assignmentId
WHERE (cf.name = "GGV Reference" AND cf.value like "%V200%")
AND (cf.name = "ABC" AND cf.value like "%GF001%") ...
Aplikacja legacy - problem
Idea ?
Tablica extra_fields
assignmentId name value
------------ ------------- ------------
123 ACF Index B00001F
123 GGV Reference 543-GGV200-1
123 Inclination +770
Nasz największy problem wydajnościowy
Kolumny w tablicy zleceń
assignmentId ACF Index GGV Reference Inclination
------------ ------------- ------------- -------------
123 B00001F 543-GGV200-1 +770
Baza dokumentowa
class AssignmentReadModel
{
/** @var int */
private $id;
/** @var string */
private $number;
// ... some other properties
/** @var array */
private $extraFields = [];
/** @var TaskReadModel[] */
private $tasks;
// ... and even more properties
Tworzymy model odczytu
Oryginalny obiekt (Assignment)
{
"id": 123,
"customFields": [
{
"name": "ACF Index",
"value": "B00001F"
},
{
"name": "GGV Reference",
"value": "543-GGV200-1"
},
{
"name": "Inclination",
"value": "+770"
}
]
}
Transformacja
Nasz Read Model (AssignmentReadModel)
{
"id": 123,
"customFields": [
{
"ACF Index": "B00001F",
"GGV Reference": "543-GGV200-1",
"Inclination": "+770"
}
]
}
Proof of Concept
Kopiowanie zawartości bazy
do klastra ElasticSearch
• Read
• Transform
• Save
• Repeat
Zanonimizowane dane produkcyjne
{
"query": {
"bool": {
"filter": [
{
"bool": {
"should": [
{
"terms": {
"search.assignment_status_id": [
1,
3
]
}
},
{
"bool": {
"must": [
{
"term": {
"search.assignment_status_id": 4
}
}
// ...
Zabawy z konsolą Elastic Search
"bool": {
"should": [
{
"bool": {
"must_not": [
{
"exists": {
"field": "extraFields.GGTV_Reference"
}
}
]
}
},
{
"terms": {
"extraFields.GGV_Gebruiker": [
"schouwer",
"uitvoerder",
"blazer",
"monteur",
"civiel",
"werkvoorbereider"
// …
Zabawy z konsolą Elastic Search
Ja pier… 0.4s !!!
Keep it in sync
• Nie ma zdarzeń systemowych
• Nie ma jednego spójnego ORM’a
• Mamy bezpośrednie użycia PDO
• Mamy Eloquent’a i Doctrine’a
• Zapytania SQL widziano nawet w plikach HTML …
Do czego się podpiąć ?
CREATE TRIGGER assignment_extra_field_update BEFORE UPDATE ON opdrachtextravelden
FOR EACH ROW
BEGIN
INSERT IGNORE INTO `dirty_assignment` (id) VALUES (NEW.OpdrachtId);
END;
CREATE TRIGGER client_update BEFORE UPDATE ON opdrachtgevers
FOR EACH ROW
BEGIN
IF (OLD.naam != NEW.naam) THEN
INSERT IGNORE INTO `dirty_assignment` (id)
SELECT O.OpdrachtId FROM opdrachten O
JOIN projecten P ON O.ProjectId = P.ProjectId
WHERE O.ProjectId = P.ProjectId AND P.OpdrachtgeverId = NEW.OpdrachtgeverId;
END IF;
END;
Ktoś pamięta jeszcze triggery na bazie danych ?
• Uruchamiana w tle
• Odczytująca zawartość tabeli dirty_assignment
• Budująca i zapisująca read modele w klastrze ES
Komenda CLI
Implementation
class GridDTO
{
/**
* @var Column[]
*/
public $columns;
/**
* @var Filter[]
*/
public $filters;
/**
* @var Row[]
*/
public $rows;
}
Grid DTO
interface GridInterface
{
public function execute(GridRequest $request): GridDTO;
public function getType(): string;
}
interface LegacyGrid extends GridInterface;
interface ElasticGrid extends GridInterface;
Extract Interface
• Możliwość włączenia i wyłączenia elasticSearch’a
• ElasticLag
• GridProvider
Ustawienia i mechanizm “fallback”
class GridProvider
{
private ElasticSetting $elasticSetting;
private ElasticLag $elasticLag;
private array $elasticGrids = [];
private array $legacyGrids = [];
public function __construct(ElasticSetting $setting, ElasticLag $lag, GridInterface ...$grids)
{
$this->elasticSetting = $setting;
$this->elasticLag = $lag;
foreach ($grids as $grid) {
if ($grid instanceof ElasticGrid) {
$this->elasticGrids[$grid->getType()] = $grid;
}
elseif ($grid instanceof LegacyGrid) {
$this->legacyGrids[$grid->getType()] = $grid;
}
}
}
Grid provider
public function getGrid(string $gridType, bool $forceLegacyGrid): GridInterface
{
if (
!$forceLegacyGrid
&& isset($this->elasticGrids[$gridType])
&& $this->elasticSetting->isEnabled()
&& !$this->lagging()
) {
return $this->elasticGrids[$gridType];
}
return $this->legacyGrids[$gridType];
}
Grid provider
class LegacyAssignmentGrid implements LegacyGrid
{
public function execute(GridRequest $request): GridDTO
{
$pdo = DBConnection::GetOpenConnection();
// ... i tutaj następuje 300 linii dotychczasowego kodu
Implementacja legacy
class ElasticAssignmentGrid extends AbstractGrid implements ElasticGrid
{
public function execute(GridRequest $request): GridDTO
{
$profile = $this->dbProfileRepository->find($request->gridId);
$grid = $this->initializeGrid($profile, $request);
$resultSet = $this->elasticAssignmentRepository
->getByRequest($request);
foreach ($resultSet->getResults() as $result) {
// we get our read model one at a time and populate Grid with it
$grid->addRow($this->prepareRowData($result));
}
Implementacja elasticSearch
public function getRows(GridRequest $request, Column ...$columns): Search
{
$qb = new QueryBuilder();
$bool = $qb->query()->bool();
$bool->addFilter($this->nullBool($qb, 'search.deleted_at'));
if (!$request->includeFinishedAssignments) {
$bool->addFilter($qb->query()->term(['search.status.is_last' => false]));
}
$this->filterByDates($request, $bool, $qb);
foreach ($columns as $column) {
$path = $this->mapper->getPathForGridKey($column->getVeldnaam());
// …
Kawałek repozytorium / query.
"ruflin/elastica": "^7.0"
Some tips for you
Schemat aplikacji nie używającej modeli odczytu
• Tylko pola wymagane w konkretnym widoku / odpowiedzi
• Pola wyliczone
• Pola przechowujące agregację
• Zmień strukturę danych jeśli chcesz
Przygotuj sobie klasę reprezentującą model odczytu
• Zde
fi
niuj interfejsy query lub repozytoriów tak żeby zwracały modele odczytu
• Dotychczasowy kod używający modelu zapisu może być ich pierwszą implementacją
• Nowe implementacje mogą czerpać dane bezpośrednio z bazy (z pominięciem ORM)
• Jeśli dopuszczamy “eventual consistency” możemy przełączyć odczyty na repliki
• Jesteś gotowy na wyniesienie modeli odczytu do innej tabeli / bazy
Określ kontrakty, wprowadź pierwsze implementacje
Po wprowadzeniu modelu odczytu
• Snapshot przygotowany w nocy
• Utrzymywana w spójności w oparciu o triggery
• Zdarzenia dostarczane przez ORM
• Zdarzenia domenowe
Synchronizacja z wydzieloną bazą odczytu
Jak skutecznie read model
Case study
Tomasz Surowiec
PHP developer
suro@tsh.io
+48 794 628 983
tsh.io
#PDK

More Related Content

More from The Software House

Feature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptFeature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptThe Software House
 
Typowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptTypowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptThe Software House
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLAutomatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLThe Software House
 
Serverless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychServerless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychThe Software House
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciTesty API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciThe Software House
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejFirestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejThe Software House
 
Jak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachJak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachThe Software House
 
O łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsO łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsThe Software House
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeAmazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeThe Software House
 
Od Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduOd Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduThe Software House
 
Co QA może i czego nie powinien się bać?
Co QA może i czego nie powinien się bać?Co QA może i czego nie powinien się bać?
Co QA może i czego nie powinien się bać?The Software House
 
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?The Software House
 
Pierwsza wycieczka nad jezioro (danych)
Pierwsza wycieczka nad jezioro (danych)Pierwsza wycieczka nad jezioro (danych)
Pierwsza wycieczka nad jezioro (danych)The Software House
 
A w więc chcesz zostać frontend developerem?
A w więc chcesz zostać frontend developerem?A w więc chcesz zostać frontend developerem?
A w więc chcesz zostać frontend developerem?The Software House
 
DynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychDynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychThe Software House
 
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event Bridge
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event BridgeKomunikacja oparta o zdarzenia z wykorzystaniem AWS Event Bridge
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event BridgeThe Software House
 
Jak poprawić Core Web Vitals w aplikacji Next.js
Jak poprawić Core Web Vitals w aplikacji Next.jsJak poprawić Core Web Vitals w aplikacji Next.js
Jak poprawić Core Web Vitals w aplikacji Next.jsThe Software House
 
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...The Software House
 

More from The Software House (20)

Feature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptFeature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScript
 
Typowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptTypowanie nominalne w TypeScript
Typowanie nominalne w TypeScript
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLAutomatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
 
Serverless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychServerless Compose vs hurtownia danych
Serverless Compose vs hurtownia danych
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciTesty API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięci
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejFirestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny Krzemowej
 
Jak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachJak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzach
 
Jak poskromić AWS?
Jak poskromić AWS?Jak poskromić AWS?
Jak poskromić AWS?
 
O łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsO łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.js
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeAmazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurze
 
Od Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduOd Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki kodu
 
Co QA może i czego nie powinien się bać?
Co QA może i czego nie powinien się bać?Co QA może i czego nie powinien się bać?
Co QA może i czego nie powinien się bać?
 
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?
Zmigrujmy 30 tys. użytkowników ze starego systemu. Co może pójść nie tak?
 
Pierwsza wycieczka nad jezioro (danych)
Pierwsza wycieczka nad jezioro (danych)Pierwsza wycieczka nad jezioro (danych)
Pierwsza wycieczka nad jezioro (danych)
 
A w więc chcesz zostać frontend developerem?
A w więc chcesz zostać frontend developerem?A w więc chcesz zostać frontend developerem?
A w więc chcesz zostać frontend developerem?
 
DynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychDynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornych
 
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event Bridge
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event BridgeKomunikacja oparta o zdarzenia z wykorzystaniem AWS Event Bridge
Komunikacja oparta o zdarzenia z wykorzystaniem AWS Event Bridge
 
DIY: React.js od zera
DIY: React.js od zeraDIY: React.js od zera
DIY: React.js od zera
 
Jak poprawić Core Web Vitals w aplikacji Next.js
Jak poprawić Core Web Vitals w aplikacji Next.jsJak poprawić Core Web Vitals w aplikacji Next.js
Jak poprawić Core Web Vitals w aplikacji Next.js
 
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
“Dziesięć serwerów poproszę!“, czyli co może Ci zaoferować definiowanie infra...
 

Jak skutecznie read model. Case study

  • 1. Jak skutecznie read model Case study Tomasz Surowiec, Gliwice 28.09.2022
  • 4. PHP Developer @ TSH Suro
  • 6. Aplikacja legacy - krótka charakterystyka biznesowa • Operuje na zleceniach wykonania usługi • Długo obecna na na rynku • Dochodowa • Elastyczna
  • 7. Pola dodatkowe • Dynamicznie definiowane przez administratora • Reguły biznesowe • Filtrowane konfigurowalne listy
  • 8. CREATE TABLE custom_fields ( assignmentId int NOT NULL, name varchar(32) NOT NULL, value varchar(128) NOT NULL, CONSTRAINT cf_key PRIMARY KEY(assignmentId,name) ) assignmentId name value ------------ ------------- ------------ 123 ACF Index B00001F 123 GGV Reference 543-GGV200-1 123 Inclination +770 EAV Encja-Atrybut-Wartość
  • 9. IF a.STATUS = ‘PENDING’ AND cf.FOO > cf.BAR SET cf.BAZ = cf.BAZ + 1 Reguły biznesowe czyli tzw workflow • Składane z klocków metodą drag and drop • Definiowane dla typu zlecenia • Mogły pobierać wartości ze zleceń powiązanych
  • 10. Gridy - dynamicznie definiowane listy zleceń
  • 11. SELECT * FROM assignment a JOIN custom_fields cf ON a.id = cf.assignmentId WHERE (cf.name = "GGV Reference" AND cf.value like "%V200%") AND (cf.name = "ABC" AND cf.value like "%GF001%") ... Aplikacja legacy - problem
  • 13. Tablica extra_fields assignmentId name value ------------ ------------- ------------ 123 ACF Index B00001F 123 GGV Reference 543-GGV200-1 123 Inclination +770 Nasz największy problem wydajnościowy Kolumny w tablicy zleceń assignmentId ACF Index GGV Reference Inclination ------------ ------------- ------------- ------------- 123 B00001F 543-GGV200-1 +770
  • 15. class AssignmentReadModel { /** @var int */ private $id; /** @var string */ private $number; // ... some other properties /** @var array */ private $extraFields = []; /** @var TaskReadModel[] */ private $tasks; // ... and even more properties Tworzymy model odczytu
  • 16. Oryginalny obiekt (Assignment) { "id": 123, "customFields": [ { "name": "ACF Index", "value": "B00001F" }, { "name": "GGV Reference", "value": "543-GGV200-1" }, { "name": "Inclination", "value": "+770" } ] } Transformacja Nasz Read Model (AssignmentReadModel) { "id": 123, "customFields": [ { "ACF Index": "B00001F", "GGV Reference": "543-GGV200-1", "Inclination": "+770" } ] }
  • 18. Kopiowanie zawartości bazy do klastra ElasticSearch • Read • Transform • Save • Repeat
  • 20. { "query": { "bool": { "filter": [ { "bool": { "should": [ { "terms": { "search.assignment_status_id": [ 1, 3 ] } }, { "bool": { "must": [ { "term": { "search.assignment_status_id": 4 } } // ... Zabawy z konsolą Elastic Search
  • 21. "bool": { "should": [ { "bool": { "must_not": [ { "exists": { "field": "extraFields.GGTV_Reference" } } ] } }, { "terms": { "extraFields.GGV_Gebruiker": [ "schouwer", "uitvoerder", "blazer", "monteur", "civiel", "werkvoorbereider" // … Zabawy z konsolą Elastic Search
  • 23. Keep it in sync
  • 24. • Nie ma zdarzeń systemowych • Nie ma jednego spójnego ORM’a • Mamy bezpośrednie użycia PDO • Mamy Eloquent’a i Doctrine’a • Zapytania SQL widziano nawet w plikach HTML … Do czego się podpiąć ?
  • 25. CREATE TRIGGER assignment_extra_field_update BEFORE UPDATE ON opdrachtextravelden FOR EACH ROW BEGIN INSERT IGNORE INTO `dirty_assignment` (id) VALUES (NEW.OpdrachtId); END; CREATE TRIGGER client_update BEFORE UPDATE ON opdrachtgevers FOR EACH ROW BEGIN IF (OLD.naam != NEW.naam) THEN INSERT IGNORE INTO `dirty_assignment` (id) SELECT O.OpdrachtId FROM opdrachten O JOIN projecten P ON O.ProjectId = P.ProjectId WHERE O.ProjectId = P.ProjectId AND P.OpdrachtgeverId = NEW.OpdrachtgeverId; END IF; END; Ktoś pamięta jeszcze triggery na bazie danych ?
  • 26. • Uruchamiana w tle • Odczytująca zawartość tabeli dirty_assignment • Budująca i zapisująca read modele w klastrze ES Komenda CLI
  • 28. class GridDTO { /** * @var Column[] */ public $columns; /** * @var Filter[] */ public $filters; /** * @var Row[] */ public $rows; } Grid DTO
  • 29. interface GridInterface { public function execute(GridRequest $request): GridDTO; public function getType(): string; } interface LegacyGrid extends GridInterface; interface ElasticGrid extends GridInterface; Extract Interface
  • 30. • Możliwość włączenia i wyłączenia elasticSearch’a • ElasticLag • GridProvider Ustawienia i mechanizm “fallback”
  • 31. class GridProvider { private ElasticSetting $elasticSetting; private ElasticLag $elasticLag; private array $elasticGrids = []; private array $legacyGrids = []; public function __construct(ElasticSetting $setting, ElasticLag $lag, GridInterface ...$grids) { $this->elasticSetting = $setting; $this->elasticLag = $lag; foreach ($grids as $grid) { if ($grid instanceof ElasticGrid) { $this->elasticGrids[$grid->getType()] = $grid; } elseif ($grid instanceof LegacyGrid) { $this->legacyGrids[$grid->getType()] = $grid; } } } Grid provider
  • 32. public function getGrid(string $gridType, bool $forceLegacyGrid): GridInterface { if ( !$forceLegacyGrid && isset($this->elasticGrids[$gridType]) && $this->elasticSetting->isEnabled() && !$this->lagging() ) { return $this->elasticGrids[$gridType]; } return $this->legacyGrids[$gridType]; } Grid provider
  • 33. class LegacyAssignmentGrid implements LegacyGrid { public function execute(GridRequest $request): GridDTO { $pdo = DBConnection::GetOpenConnection(); // ... i tutaj następuje 300 linii dotychczasowego kodu Implementacja legacy
  • 34. class ElasticAssignmentGrid extends AbstractGrid implements ElasticGrid { public function execute(GridRequest $request): GridDTO { $profile = $this->dbProfileRepository->find($request->gridId); $grid = $this->initializeGrid($profile, $request); $resultSet = $this->elasticAssignmentRepository ->getByRequest($request); foreach ($resultSet->getResults() as $result) { // we get our read model one at a time and populate Grid with it $grid->addRow($this->prepareRowData($result)); } Implementacja elasticSearch
  • 35. public function getRows(GridRequest $request, Column ...$columns): Search { $qb = new QueryBuilder(); $bool = $qb->query()->bool(); $bool->addFilter($this->nullBool($qb, 'search.deleted_at')); if (!$request->includeFinishedAssignments) { $bool->addFilter($qb->query()->term(['search.status.is_last' => false])); } $this->filterByDates($request, $bool, $qb); foreach ($columns as $column) { $path = $this->mapper->getPathForGridKey($column->getVeldnaam()); // … Kawałek repozytorium / query. "ruflin/elastica": "^7.0"
  • 37. Schemat aplikacji nie używającej modeli odczytu
  • 38. • Tylko pola wymagane w konkretnym widoku / odpowiedzi • Pola wyliczone • Pola przechowujące agregację • Zmień strukturę danych jeśli chcesz Przygotuj sobie klasę reprezentującą model odczytu
  • 39. • Zde fi niuj interfejsy query lub repozytoriów tak żeby zwracały modele odczytu • Dotychczasowy kod używający modelu zapisu może być ich pierwszą implementacją • Nowe implementacje mogą czerpać dane bezpośrednio z bazy (z pominięciem ORM) • Jeśli dopuszczamy “eventual consistency” możemy przełączyć odczyty na repliki • Jesteś gotowy na wyniesienie modeli odczytu do innej tabeli / bazy Określ kontrakty, wprowadź pierwsze implementacje
  • 41. • Snapshot przygotowany w nocy • Utrzymywana w spójności w oparciu o triggery • Zdarzenia dostarczane przez ORM • Zdarzenia domenowe Synchronizacja z wydzieloną bazą odczytu
  • 42. Jak skutecznie read model Case study Tomasz Surowiec PHP developer suro@tsh.io +48 794 628 983 tsh.io
  • 43. #PDK