sobota, 6 listopada 2021

Źródło danych InMemory

Często dodając nowe funkcjonalności opieramy się na schemacie: 

  1. pobieranie danych ze bazy danych...
  2. ...przeprowadzanie na nich pewnych operacji

    Niekiedy dane te nie zmieniają się w czasie, są z góry ustalone i w związku z tym nie muszą być umieszczane w zewnętrznych bazach typu MySQL/Redis. Możemy bardziej skłaniać się ku przechowywaniu ich bezpośrednio w repozytorium kodu PHP. Przykładowymi danymi tego typu może być tablica asocjacyjna reprezentująca flow zmiany statusów jakiejś konkretnej Encji. Tak mogłaby prezentować się jej przykładowa implementacja:

namespace App\Context\Domain;

final class ChangeStatusProvider 
{
	public static function mapping(): array 
	{
		return [
			1 => 4,
			2 => 5,
			3 => 5
		];
	}
}

Albo z wykorzystaniem stałych:

namespace App\Context\Domain;

interface ChangeStatusProvider 
{
	public const MAPPING = [
		1 => 4,
		2 => 5,
		3 => 5
	];
}

    Klasa tego typu sama w sobie nie nastręcza problemów, ale sposób jej wykorzystania przez klienta już może. Jako klienta możemy wyobrazić sobie Value Object, który jest polem Encji, a ta elementem Agregatu. Można założyć, że ta tablica asocjacyjna nie może znajdować się bezpośrednio wewnątrz Value Object'u ponieważ jest wykorzystywana również przez innego klienta w zupełnie innym kontekście.

ValueObject wywołuje wewnętrznie metodę statyczną w celu pobrania danych.

Wspomniany Value Object będzie odpowiadał za zwracanie nowego statusu, pytać o niego będzie Encja, a Encje - Agregat.  

namespace App\Context\Domain\ValueObject;

use App\Context\Domain\ChangeStatusProvider;

final class SomeValueObject 
{
	// ...
	public function nextStatus(): int
	{
		$newStatus = ChangeStatusProvider::MAPPING($this->status);
		//...
		return $newStatus;
	}
	//...
}

    Dla klienta (Encji) nie ma różnicy czy otrzymany nowy status został wstrzyknięty przez konstruktor Value Object'u, czy tak jak teraz - pochodzi z metody statycznej. Nie ma znaczenia bo otrzymuje gotowy Value Object jako parametr konstruktora. Dla niego wygląda po prostu jak integralna część Value Object. Wszystko więc wygląda dobrze do czasu...

Zmiany wymagań biznesowych

    Okazuje się że, z czasem Biznes oczekuje konfigurowalnych zmian stanu. Administratorzy aplikacji mają mieć możliwość modyfikowania flow zmiany stanu według określonych ram. Dla developerów będzie to oznaczało, że mapowania statusów nie będą dłużej realizowane przez klasę/interfejs ChangeStatusProvider. Zostanie utworzona tabela w relacyjnej bazie danych + prosty interfejs do zarządzania jej zawartością. 

    W obszarze aplikacji przedstawionym w przykładzie, musimy dokonać zmian. Value Object musi teraz otrzymywać dane z zewnątrz (poprzez konstruktor). Dane o mapowaniu statusów pochodzić będą z nowo utworzonego Repozytorium. Jeżeli instancje Value Object tworzymy wewnątrz Serwisu Aplikacyjnego/Fabryki to tam będzie musiało być wstrzyknięte wspomniane Repozytorium. Gorzej jeżeli odbywa się to wewnątrz Agregatu lub Encji - wtedy dane z Repozytorium będą musiały być przekazane przez kolejne warstwy co jest utrudnieniem w implementacji zmian (jest to ciekawy temat na inny wpis).     

    W tym przypadku, do tego typu implementacji nigdy by nie doszło gdyby developer nie zdecydował się na przechowywanie danych w metodzie statycznej (lub stałych klasy). Głównym czynnikiem, który za to odpowiada jest nierozważanie danych z metody statycznej jako danych zewnętrznych. Gdyby od razu zostało przyjęte takie założenie, programista pewnie z automatu

1. utworzyłby interfejs Repozytorium (Warstwa Aplikacji)

namespace App\Context\Application;

interface StatusRepository 
{
	/** @return array<int, int> */
	public function mapping(): array;
}

2. jego implementacje (Warstwa Infrastruktury)

namespace App\Context\Infrastructure;

use App\Context\Application\StatusRepository; 

final class StatusInMemoryRepository implements StatusRepository 
{
	/** @inheritDoc */
	public function mapping(): array 
	{
		return [
			1 => 4,
			2 => 5,
			3 => 5
		];
	}
}

3. dane do modeli domenowych (Encji/Value Object'ów) wprowadzane by były przez ich konstruktor/metody

namespace App\Context\Application;

final class AggregateFactory 
{
	public function __construct(
		// ...
		private StatusRepository $statusRepository,
		// ...
	) {}

	public function create(/*...*/): Aggregate 
	{
		// ...
		$someValueObject = new SomeValueObject(
			// ...
			$this->statusRepository->mapping()
			// ...
		);
		// ...
	}
}

 

    Dlatego istotne jest by - za każdym razem gdy zamierzamy umieścić dane w kodzie PHP - zastanowić się czy nie będzie prowadziło to do wyżej opisanych problemów w przyszłości. Mając to na uwadze, możemy ułatwić sobie i innym utrzymanie i rozwój projektu. A więc zamiast refaktoryzować metody statyczne w kierunku Wzorca Repozytorium, należy zaimplementować od razu takie rozwiązanie. Dodatkowo należy pilnować by dane do Agregatów, Encji, Value Object'ów przekazywane były przez ich konstruktory.   

Brak komentarzy:

Prześlij komentarz