Srijith Rajamohan, Ph.D.
sjster@gmail.com
Machine Learning/ Data Science
Mathias Arlaud
mtarld
@matarld
les-tilleuls.coop
Network Map of Entities
Interactive Neighborhood Filtering
Summary
- 3 Coursera courses on Bayesian Modeling
- Writing
- Articles on ML and DS on Databricks.com
- Publish articles on popular Medium publications such as Towards Data Science
- Thought-leadership articles
- Work with Product teams
- ML Product team
- MLflow team
- PySpark team
- Talks on MLflow, Deep Learning at Databricks, End-to-end ML at Scale, Bayesian Modeling
- Worked with the SMEs on DL and NLP
- Helped with the messaging architecture and redesign of the Spark website
Architecture
Our tiny panda
- Doctrine
- Schema
- API Platform
- Resource
- PHP
- Object
@matarld
@chalas_r
// src/Domain/Forest/Model/Panda.php #[ORM\Entity] #[ApiResource] final class Panda { public function __construct( private UuidInterface $uuid, // ... #[ORM\Column(type: 'int')] private int $hungerAmount, ) { } // ... public function isHungry(): bool { return $this->hungerAmount > self::HUNGERNESS_THRESHOLD; } public function feed(Bamboo $bamboo): void { $this->hungerAmount = min(0, $this->hunger - $bamboo->getSize()); } }
@matarld
@chalas_r
Decouple from Doctrine
#[ORM\Entity] #[ApiResource] final class Panda { public function __construct( private UuidInterface $uuid, // ... #[ORM\Column(type: 'int')] private int $hunger, ) { } // ... }
<!-- src/Infrastructure/Forest/Doctrine/Mapping/Panda.xml --> <entity name="Acme\Domain\Forest\Model\Panda" table="panda"> <field name="uuid" type="uuid" unique="true" /> <!-- ... --> <field name="hungry" type="integer" /> </entity>
@matarld
@chalas_r
Decouple from API Platform
#[ApiResource] final class Panda { public function __construct( private UuidInterface $uuid, // ... private int $hunger, ) { } // ... }
<!-- src/Infrastructure/Forest/ApiPlatform/Resource/Panda.xml --> <resource class="Acme\Domain\Forest\Model\Panda"> <itemOperations><!-- ... --></itemOperations> <collectionOperations><!-- ... --></collectionOperations> </resource>
Finding pandas
> curl /api/pandas/{uuid}
- API Platform
- ReadListener
@matarld
@chalas_r
- ChainItemDataProvider
[resourceClass, operationName, context]
- Doctrine
- ItemDataProvider
Finding pandas
> curl /api/pandas/{uuid}
- API Platform
- ReadListener
- ChainItemDataProvider
[resourceClass, operationName, context]
- DependencyInjection
- Compiler Pass
- Clearing tags
- Our DataProvider
@matarld
@chalas_r
Command/Query pattern
@matarld
@chalas_r
- Command
- Domain
- Infra
- Query
Query based data provider
// src/Application/Forest/Query/FindPandaByUuidQuery.php final class FindPandaByUuidQuery extends FindByUuidQuery { }
- FindByUuidQuery
@matarld
@chalas_r
// src/Application/Forest/Query/FindPandaByUuidQueryHandler.php use Acme\Domain\Forest\Repository\PandaRepository; final class FindPandaByUuidQueryHandler implements QueryHandler { public function __construct(private PandaRepository $repository) {} final public function __invoke(FindPandaByUuidQuery $query): ?Panda { return $this->repository->searchByUuid($query->uuid); } }
Query based data provider
FindPandaByUuidQuery
- Messenger
- Query bus
Panda
- A new resource operation attribute
@matarld
@chalas_r
<itemOperation name="get"> <attribute name="query">FindPandaByUuidQuery</attribute> </itemOperation>
// src/Infrastructure/Shared/ApiPlatform/DataProvider/ItemQueryDataProvider.php final class ItemQueryDataProvider implements DataProviderInterface { public function getItem(...): ?object { $queryClass = $this->resourceMetadataFactory ->create($resourceClass) ->getItemOperationAttribute($operationName, 'query'); return $this->queryBus->ask(new $queryClass($identifiers['uuid'])); } public function supports(...): bool { /* ... */} }
- ItemQueryDataProvider
- Check that we have a query attribute defined
- Ensure that's a
FindByUuidQuery
- DependencyInjection
- Compiler Pass
Query based data provider
@matarld
@chalas_r
// src/Infrastructure/Shared/ApiPlatform/DataProvider/CollectionQueryDataProvider.php final class CollectionQueryDataProvider implements DataProviderInterface { public function getCollection(...): iterable { $queryClass = $this->resourceMetadataFactory ->create($resourceClass) ->getCollectionOperationAttribute($operationName, 'query'); return $this->queryBus->ask($queryClass::fromContext($context)); } }
<collectionOperation name="get"> <attribute name="query">FindPandasQuery</attribute> </collectionOperation>
- CollectionQueryDataProvider
Query based data provider
FindPandasQuery
Pandas
FindByCriteriaQuery
@matarld
@chalas_r
- Custom DataProvider
Query based data provider
FindExtraterrialPandasQuery
- Custom DataProvider
- ExtraterrialPandaDataProvider
FindPandaByUuidQuery
- CRUD DataProvider
- ItemQueryDataProvider
// src/Infrastructure/Shared/ApiPlatform/DataPersister/DataPersister.php final class DataPersister implements DataPersisterInterface { public function persist(...): ?object { $commandClass = $this->resourceMetadataFactory->create($resourceClass) ->getOperationAttribute($context, 'command'); return $this->commandBus->dispatch($commandClass::fromModel($data)); } public function remove(...): void { $commandClass = $this->resourceMetadataFactory->create($resourceClass) ->getOperationAttribute($context, 'command'); return $this->commandBus->dispatch($commandClass::fromModel($data)); } }
- DataPersister
Command based data persister
@matarld
@chalas_r
PersistCommand
RemoveCommand
Command based data persister
- Commands
// src/Application/Forest/Command/RemovePandaCommandHandler.php final class RemovePandaCommandHandler implements CommandHandler { public function __construct(private PandaRepository $repository) {} public function __invoke(RemovePandaCommand $command): void { $this->repository->remove($command->id()); } }
@matarld
@chalas_r
// src/Application/Forest/Command/RemovePandaCommand.php final class RemovePandaCommand implements RemoveCommand { public function __construct(private int $id) {} public static function fromModel(object $panda): self { return new self($panda->id()); } }
Command based data persister
- Commands
@matarld
@chalas_r
<itemOperation name="remove"> <attribute name="query">FindPandaByUuidQuery</attribute> <attribute name="command">RemovePandaCommand</attribute> </itemOperation>
- Delete it
- Find a panda by its uuid
In a nutshell
- ItemQueryDataProvider
- FindByUuidQuery
- CollectionQueryDataProvider
- FindByCriteriaQuery
- DataPersister
- PersistCommand
- DataPersister
- RemoveCommand
- API Platform
- ReadListener
- API Platform
- WriteListener
@matarld
@chalas_r
Anemic models
- Use case
final class Panda { public function __construct(private string $name) { } public function getName(): string { return $this->name; } public function setName(string $name): void { $this->name = $name; } }
{"name": "Pedro Panda"}
PATCH
- Symfony
- PropertyAccessor
- Public properties
- Setters
- Constructor arguments
final class Panda { public function __construct(private string $name) { } public function name(): string { return $this->name; } public function rename(string $firstname, string $lastname): void { $this->name = sprintf('%s %s', $firstname, $lastname); } }
Public properties
Setters
Constructor arguments
@matarld
@chalas_r
Rich models
DTOs
- Data Transfer Objects
@matarld
@chalas_r
DTOs
- Payload and views
{ "uuid": "...", "name": "Pedro Panda", "hungry": true }
{ "firstname": "Pedro", "lastname": "Panda" }
final class Panda { public function __construct( private UuidInterface $uuid, private string $name, private int $hunger, ) { } // ... }
- Payload
- Model
- View
- Concatenate firstname and lastname
- Convert hunger to "hungry" boolean
@matarld
@chalas_r
DTOs
- API Platform configuration
<itemOperation name="get"> <attribute name="input">Acme\Application\Forest\Payload\PandaPayload</attribute> <attribute name="output">Acme\Application\Forest\View\PandaView</attribute> </itemOperation>
- API Platform
- DeserializeListener
- API Platform
- SerializeListener
@matarld
@chalas_r
Payload
- Input DTO
// src/Application/Forest/Payload/PandaPayload.php final class PandaPayload { public function __construct( public readonly ?string $firstname = null, public readonly ?string $lastname = null, ) { } }
// src/Domain/Forest/Model/Panda.php final class Panda { public function __construct(private string $name) { } public function rename( string $firstname, string $lastname, ): void { $this->name = sprintf('%s %s', $firstname, $lastname); } }
- PandaPayloadDataTransformer
- Use rename method
@matarld
@chalas_r
Payload
- PayloadDataTransformer
// src/Infrastructure/Forest/ApiPlatform/DataTransformer/PandaPayloadDataTransformer.php final class PandaPayloadDataTransformer implements DataTransformerInterface { public function transform($payload, string $to, array $context = []) { $panda = $context['object_to_populate'] ?? new Panda(); $panda->rename($payload->firstname, $payload->lastname); return $panda; } public function supportsTransformation($data, string $to, array $context = []): bool { return PandaPayload::class === ($context['input']['class'] ?? null) && Panda::class === $to; } }
@matarld
@chalas_r
View
- Output DTO
// src/Domain/Forest/Model/Panda.php final class Panda { public function __construct( private UuidInterface $uuid private string $name, private int $hunger, ) { } public function isHungry(): bool { return $this->hunger > self::HUNGERNESS_THRESHOLD; } }
// src/Application/Forest/View/PandaView.php final class PandaView { public function __construct( public readonly string $uuid, public readonly string $name, public readonly bool $hungry, ) { } }
- PandaViewDataTransformer
- Cast uuid, use isHungry method
@matarld
@chalas_r
View
- ViewDataTransformer
// src/Infrastructure/Forest/ApiPlatform/DataTransformer/PandaViewDataTransformer.php final class PandaViewDataTransformer implements DataTransformer { public function transform($panda, string $to, array $context = []) { return PandaView((string) $panda->uuid(), $panda->name(), $panda->isHungry()); } public function supportsTransformation($data, string $to, array $context = []): bool { return PandaView::class === $to && $data instanceof Panda; } }
@matarld
@chalas_r
To Summarize
@matarld
@chalas_r
- API Platform is fully extensible.
- Don't do this for your personal blog!
- Take back control over the domain
- Embrace complexity
- Write more scalable & testable code
To Summarize
@matarld
@chalas_r
Thanks!
@matarld
@chalas_r
@coopTilleuls
Copy of [Forum PHP] DDD & APIP
By sjster
Copy of [Forum PHP] DDD & APIP
- 204