Event Engine has a nice feature called Flavours. A Flavour lets you customize the way Event Engine interacts with your code. Throughout the tutorial we worked with the PrototypingFlavour, which is the default.
As the name suggests, the PrototypingFlavour is optimized for rapid development. For example instead of defining classes
for each type of message, Event Engine passes its default Message
implementation to aggregate functions, process managers,
resolvers and projectors. You don't need to care about serialization and mapping.
If you want to try out new ideas, PrototypingFlavour is your best friend. Following Domain-Driven Design best practices Continuous Discovery and Agile Development are key drivers for successful projects. This requires experimentation and with the PrototypingFlavour it's easier than ever.
Experimentation is great, but at some point you'll be satisfied with the domain model and want to turn it into a clean and
robust implementation. That's very important for long-lived applications. Fortunately, Event Engine offers two additional Flavours.
One is called the FunctionalFlavour and the other one OopFlavour. Finally, you can implement your own EventEngine\Runtime\Flavour
to turn Event Engine into your very own CQRS / ES framework.
First let's look at the FunctionalFlavour. It's similar to what we did so far, except that explicit message types are used instead of generic Event Engine messages.
The FunctionalFlavour requires an implementation of EventEngine\Runtime\Functional\Port
. Here you have to define custom mapping and serialization
logic for message types. Create a new class MyServiceMessagePort
in src/System/Flavour
:
<?php
declare(strict_types=1);
namespace MyService\System\Flavour;
use EventEngine\Messaging\CommandDispatchResult;
use EventEngine\Messaging\Message;
use EventEngine\Messaging\MessageBag;
use EventEngine\Runtime\Functional\Port;
final class MyServiceMessagePort implements Port
{
/**
* @param Message $message
* @return mixed The custom message
*/
public function deserialize(Message $message)
{
// TODO: Implement deserialize() method.
}
/**
* @param mixed $customMessage
* @return array
*/
public function serializePayload($customMessage): array
{
// TODO: Implement serializePayload() method.
}
/**
* @param mixed $customCommand
* @return MessageBag
*/
public function decorateCommand($customCommand): MessageBag
{
// TODO: Implement decorateCommand() method.
}
/**
* @param mixed $customEvent
* @return MessageBag
*/
public function decorateEvent($customEvent): MessageBag
{
// TODO: Implement decorateEvent() method.
}
/**
* @param string $aggregateIdPayloadKey
* @param mixed $command
* @return string
*/
public function getAggregateIdFromCommand(string $aggregateIdPayloadKey, $command): string
{
// TODO: Implement getAggregateIdFromCommand() method.
}
/**
* @param mixed $customCommand
* @param mixed $preProcessor Custom preprocessor
* @return mixed|CommandDispatchResult Custom message or CommandDispatchResult
*/
public function callCommandPreProcessor($customCommand, $preProcessor)
{
// TODO: Implement callCommandPreProcessor() method.
}
/**
* Commands returned by the controller are dispatched automatically
*
* @param mixed $customCommand
* @param mixed $controller
* @return mixed[]|null|CommandDispatchResult Array of custom commands or null|CommandDispatchResult to indicate that no further action is required
*/
public function callCommandController($customCommand, $controller)
{
// TODO: Implement callCommandController() method.
}
/**
* @param mixed $customCommand
* @param mixed $contextProvider
* @return mixed
*/
public function callContextProvider($customCommand, $contextProvider)
{
// TODO: Implement callContextProvider() method.
}
public function callResolver($customQuery, $resolver)
{
// TODO: Implement callResolver() method.
}
}
We'll implement the interface step by step and define a mapping strategy along the way.
First method is deserialize
:
/**
* @param Message $message
* @return mixed The custom message
*/
public function deserialize(Message $message)
{
// TODO: Implement deserialize() method.
}
An Event Engine message is passed as argument and the method should return a custom message. The first important decision is required:
Which serialization technique do we want to use? Some people prefer handcrafted serialization, while others prefer conventions or serializers. The good news is, every technique can be used! It just needs to be implemented in the Port.
To keep the tutorial simple, we're going to use the tools shipped with Event Engine.
That said, our messages become ImmutableRecord
s and use the built-in serialization technique provided by ImmutableRecordLogic
.
The fact that messages are still coupled with the framework is not important here. It's our decision as developers to do it, but nothing required by Event Engine. We could also write our own serialization mechanism or use a third-party tool like FPP.
Let's create some types and messages first:
src/Domain/Model/Building/BuildingId.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
final class BuildingId
{
private $buildingId;
public static function generate(): self
{
return new self(Uuid::uuid4());
}
public static function fromString(string $buildingId): self
{
return new self(Uuid::fromString($buildingId));
}
private function __construct(UuidInterface $buildingId)
{
$this->buildingId = $buildingId;
}
public function toString(): string
{
return $this->buildingId->toString();
}
public function equals($other): bool
{
if (!$other instanceof self) {
return false;
}
return $this->buildingId->equals($other->buildingId);
}
public function __toString(): string
{
return $this->buildingId->toString();
}
}
src/Domain/Model/Building/BuildingName.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building;
final class BuildingName
{
private $name;
public static function fromString(string $name): self
{
return new self($name);
}
private function __construct(string $name)
{
$this->name = $name;
}
public function toString(): string
{
return $this->name;
}
public function equals($other): bool
{
if(!$other instanceof self) {
return false;
}
return $this->name === $other->name;
}
public function __toString(): string
{
return $this->name;
}
}
src/Domain/Model/Building/Username.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building;
final class Username
{
private $name;
public static function fromString(string $name): self
{
return new self($name);
}
private function __construct(string $name)
{
$this->name = $name;
}
public function toString(): string
{
return $this->name;
}
public function equals($other): bool
{
if(!$other instanceof self) {
return false;
}
return $this->name === $other->name;
}
public function __toString(): string
{
return $this->name;
}
}
src/Domain/Model/Building/Command/AddBuilding.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Command;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\BuildingName;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class AddBuilding implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var BuildingName
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return BuildingName
*/
public function name(): BuildingName
{
return $this->name;
}
}
src/Domain/Model/Building/Command/CheckInUser.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Command;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class CheckInUser implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Model/Building/Command/CheckOutUser.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Command;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class CheckOutUser implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
Ok, much more classes now. Each property has its own value object like BuildingId
, BuildingName
and Username
. Again, that's not a requirement but it adds type safety to
the implementation and serves as documentation. Don't worry about the amount of code. Most of it can be generated using PHPStorm templates. Event Engine docs contain useful tips.
Another possibility is the already mentioned library FPP.
With the value objects in place we've added a class for each command and implemented them as immutable records. Now we need a factory to instantiate a command with information
taken from Event Engine messages. MyService\Domain\Api\Command
already contains command specific information. Let's add the factory there.
<?php
declare(strict_types=1);
namespace MyService\Domain\Api;
use MyService\Domain\Model\Building\Command\AddBuilding;
use MyService\Domain\Model\Building\Command\CheckInUser;
use MyService\Domain\Model\Building\Command\CheckOutUser;
use EventEngine\EventEngine;
use EventEngine\EventEngineDescription;
use EventEngine\JsonSchema\JsonSchema;
class Command implements EventEngineDescription
{
const ADD_BUILDING = 'AddBuilding';
const CHECK_IN_USER = 'CheckInUser';
const CHECK_OUT_USER = 'CheckOutUser';
const CLASS_MAP = [
self::ADD_BUILDING => AddBuilding::class,
self::CHECK_IN_USER => CheckInUser::class,
self::CHECK_OUT_USER => CheckOutUser::class,
];
public static function createFromNameAndPayload(string $commandName, array $payload)
{
$class = self::CLASS_MAP[$commandName] ?? false;
if($class === false) {
throw new \InvalidArgumentException("Unknown command name: $commandName");
}
//Commands use ImmutableRecordLogic and therefor have a fromArray method
return $class::fromArray($payload);
}
public static function nameOf($command): string
{
$name = array_search(\get_class($command), self::CLASS_MAP);
if($name === false) {
throw new \InvalidArgumentException("Unknown command. Cannot find a name for class: " . \get_class($command));
}
return $name;
}
/* ... */
}
Finally, the factory can be used in the Port:
src/System/Flavour/MyServiceMessagePort.php
/**
* @param Message $message
* @return mixed The custom message
*/
public function deserialize(Message $message)
{
switch ($message->messageType()) {
case Message::TYPE_COMMAND:
return Command::createFromNameAndPayload($message->messageName(), $message->payload());
break;
}
}
A similar implementation is required for events and queries:
src/Domain/Model/Building/Event/BuildingAdded.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Event;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\BuildingName;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class BuildingAdded implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var BuildingName
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return BuildingName
*/
public function name(): BuildingName
{
return $this->name;
}
}
src/Domain/Model/Building/Event/UserCheckedIn.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Event;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class UserCheckedIn implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Model/Building/Event/DoubleCheckInDetected.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Event;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class DoubleCheckInDetected implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Model/Building/Event/UserCheckedOut.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Event;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class UserCheckedOut implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Model/Building/Event/DoubleCheckOutDetected.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Event;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\Username;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class DoubleCheckOutDetected implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var Username
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Api/Event.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Api;
use MyService\Domain\Model\Building\Event\BuildingAdded;
use MyService\Domain\Model\Building\Event\DoubleCheckInDetected;
use MyService\Domain\Model\Building\Event\DoubleCheckOutDetected;
use MyService\Domain\Model\Building\Event\UserCheckedIn;
use MyService\Domain\Model\Building\Event\UserCheckedOut;
use EventEngine\EventEngine;
use EventEngine\EventEngineDescription;
use EventEngine\JsonSchema\JsonSchema;
class Event implements EventEngineDescription
{
const BUILDING_ADDED = 'BuildingAdded';
const USER_CHECKED_IN = 'UserCheckedIn';
const USER_CHECKED_OUT = 'UserCheckedOut';
const DOUBLE_CHECK_IN_DETECTED = 'DoubleCheckInDetected';
const DOUBLE_CHECK_OUT_DETECTED = 'DoubleCheckOutDetected';
const CLASS_MAP = [
self::BUILDING_ADDED => BuildingAdded::class,
self::USER_CHECKED_IN => UserCheckedIn::class,
self::USER_CHECKED_OUT => UserCheckedOut::class,
self::DOUBLE_CHECK_IN_DETECTED => DoubleCheckInDetected::class,
self::DOUBLE_CHECK_OUT_DETECTED => DoubleCheckOutDetected::class,
];
public static function createFromNameAndPayload(string $eventName, array $payload)
{
$class = self::CLASS_MAP[$eventName] ?? false;
if($class === false) {
throw new \InvalidArgumentException("Unknown event name: $eventName");
}
//Events use ImmutableRecordLogic and therefor have a fromArray method
return $class::fromArray($payload);
}
public static function nameOf($event): string
{
$name = array_search(\get_class($event), self::CLASS_MAP);
if($name === false) {
throw new \InvalidArgumentException("Unknown event. Cannot find a name for class: " . \get_class($event));
}
return $name;
}
/* ... */
}
src/Domain/Resolver/Query/GetBuilding.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver\Query;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
use MyService\Domain\Model\Building\BuildingId;
final class GetBuilding implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
}
src/Domain/Resolver/Query/GetBuildings.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver\Query;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class GetBuildings implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var string|null
*/
private $buildingNameFilter;
/**
* @return string|null
*/
public function buildingNameFilter(): ?string
{
return $this->buildingNameFilter;
}
}
src/Domain/Resolver/Query/GetUserBuildingList.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver\Query;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
use MyService\Domain\Model\Building\Username;
final class GetUserBuildingList implements ImmutableRecord
{
use ImmutableRecordLogic;
/**
* @var Username
*/
private $name;
/**
* @return Username
*/
public function name(): Username
{
return $this->name;
}
}
src/Domain/Api/Query.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Api;
use EventEngine\EventEngine;
use EventEngine\EventEngineDescription;
use EventEngine\JsonSchema\JsonSchema;
use EventEngine\Messaging\MessageBag;
use MyService\Domain\Resolver\BuildingResolver;
use MyService\Domain\Resolver\Query\GetBuilding;
use MyService\Domain\Resolver\Query\GetBuildings;
use MyService\Domain\Resolver\Query\GetUserBuildingList;
use MyService\Domain\Resolver\UserBuildingResolver;
use MyService\System\Api\SystemQuery;
class Query implements EventEngineDescription
{
/**
* Default Query, used to perform health checks using messagebox endpoint
*/
const HEALTH_CHECK = 'HealthCheck';
const BUILDING = 'Building';
const BUILDINGS = 'Buildings';
const USER_BUILDING = 'UserBuilding';
const CLASS_MAP = [
self::BUILDING => GetBuilding::class,
self::BUILDINGS => GetBuildings::class,
self::USER_BUILDING => GetUserBuildingList::class,
];
public static function createFromNameAndPayload(string $queryName, array $payload)
{
if($queryName === SystemQuery::HEALTH_CHECK) {
return new MessageBag(
SystemQuery::HEALTH_CHECK,
MessageBag::TYPE_QUERY,
[]
);
}
$class = self::CLASS_MAP[$queryName] ?? false;
if($class === false) {
throw new \InvalidArgumentException("Unknown query name: $queryName");
}
//Queries use ImmutableRecordLogic and therefor have a fromArray method
return $class::fromArray($payload);
}
public static function nameOf($query): string
{
if($query instanceof MessageBag) {
return $query->messageName();
}
$name = array_search(\get_class($query), self::CLASS_MAP);
if($name === false) {
throw new \InvalidArgumentException("Unknown query. Cannot find a name for class: " . \get_class($query));
}
return $name;
}
/* ... */
}
src/System/Flavour/MyServiceMessagePort.php
/**
* @param Message $message
* @return mixed The custom message
*/
public function deserialize(Message $message)
{
switch ($message->messageType()) {
case Message::TYPE_COMMAND:
return Command::createFromNameAndPayload($message->messageName(), $message->payload());
case Message::TYPE_EVENT:
return Event::createFromNameAndPayload($message->messageName(), $message->payload());
case Message::TYPE_QUERY:
return Query::createFromNameAndPayload($message->messageName(), $message->payload());
}
}
To convert our own message types to Event Engine messages we have to implement the serializePayload
method:
src/System/Flavour/MyServiceMessagePort.php
/**
* @param mixed $customMessage
* @return array
*/
public function serializePayload($customMessage): array
{
if(is_array($customMessage)) {
return $customMessage;
}
if(!$customMessage instanceof ImmutableRecord) {
throw new \RuntimeException(
"Invalid message passed to " . __METHOD__
. ". Should be an immutable record, but got "
. (\is_object($customMessage)? \get_class($customMessage) : \gettype($customMessage)));
}
return $customMessage->toArray();
}
decorateCommand
and decorateEvent
are special methods called for each dispatched command and all yielded events.
The expected return type is EventEngine\Messaging\MessageBag
. You can think of it as an envelop
for custom messages. The MessageBag can be used to add metadata information. Event Engine
adds information like aggregate id, aggregate type, aggregate version, causation id (command id) and causation name (command name)
by default. If you want to add additional metadata, just pass it to the MessageBag constructor (optional argument).
Decorating a custom event with a MessageBag has the advantage that a custom message can be carried through the Event Engine layer without serialization. Event Engine assumes a normal message and adds aggregate specific metadata like described above. The MessageBag is then passed back to the configured flavour to call a corresponding apply function. The flavour can access the decorated event and pass it to the function. All without serialization in between. A similar approach is used when commands are passed to preprocessors or controllers (concepts not included in the tutorial, but you can read about them in the docs).
src/System/Flavour/MyServiceMessagePort.php
/**
* @param mixed $customCommand
* @return MessageBag
*/
public function decorateCommand($customCommand): MessageBag
{
return new MessageBag(
Command::nameOf($customCommand),
MessageBag::TYPE_COMMAND,
$customCommand
//, [] <- you could add additional metadata here
);
}
/**
* @param mixed $customEvent
* @return MessageBag
*/
public function decorateEvent($customEvent): MessageBag
{
return new MessageBag(
Event::nameOf($customEvent),
MessageBag::TYPE_EVENT,
$customEvent
//, [] <- you could add additional metadata here
);
}
Event Engine has a built-in way to locate existing aggregates using a generic command handler and repository. But it needs the correct aggregateId. Each command should contain the same aggregateId property. Remember that this information is part of an Event Engine description:
src/Api/Aggregate
<?php
declare(strict_types=1);
namespace MyService\Domain\Api;
use MyService\Domain\Model\Building;
use EventEngine\EventEngine;
use EventEngine\EventEngineDescription;
class Aggregate implements EventEngineDescription
{
const BUILDING = 'Building';
/**
* @param EventEngine $eventEngine
*/
public static function describe(EventEngine $eventEngine): void
{
$eventEngine->process(Command::ADD_BUILDING)
->withNew(self::BUILDING)
->identifiedBy(Payload::BUILDING_ID) //<-- AggregateId payload property
->handle([Building::class, 'add'])
->recordThat(Event::BUILDING_ADDED)
->apply([Building::class, 'whenBuildingAdded']);
/* ... */
}
}
Each Building
command should have a buildingId
property. Our newly created commands have buildingId()
methods that we could call.
An explicit implementation looks like this:
/**
* @param string $aggregateIdPayloadKey
* @param mixed $command
* @return string
*/
public function getAggregateIdFromCommand(string $aggregateIdPayloadKey, $command): string
{
if($command instanceof AddBuilding
|| $command instanceof CheckInUser
|| $command instanceof CheckOutUser) {
return $command->buildingId()->toString();
}
throw new \RuntimeException("Unknown command. Cannot get aggregate id from it. Got " . get_class($command));
}
But we would need to remember adding a new command here each time we add a new one to the system. That's annoying and interrupts the flow.
Instead we can define an AggregateCommand
interface that each command should implement.
src/Domain/Model/Base/AggregateCommand.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Base;
interface AggregateCommand
{
public function aggregateId(): string;
}
src/Domain/Model/Building/Command/AddBuilding.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building\Command;
use MyService\Domain\Model\Base\AggregateCommand;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Model\Building\BuildingName;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class AddBuilding implements ImmutableRecord, AggregateCommand //<-- Implement new interface
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var BuildingName
*/
private $name;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return BuildingName
*/
public function name(): BuildingName
{
return $this->name;
}
public function aggregateId(): string //<-- new method
{
return $this->buildingId->toString();
}
}
Do the same for CheckInUser
and CheckOutUser
!
Done? Great! Then we can change the Port to handle any AggregateCommand
:
src/System/Flavour/MyServiceMessagePort.php
/**
* @param string $aggregateIdPayloadKey
* @param mixed $command
* @return string
*/
public function getAggregateIdFromCommand(string $aggregateIdPayloadKey, $command): string
{
if($command instanceof AggregateCommand) {
return $command->aggregateId();
}
throw new \RuntimeException("Unknown command. Cannot get aggregate id from it. Got " . get_class($command));
}
We don't know command preprocessors yet. In short: a command preprocessor can be called before a command is passed to an aggregate function. This can be useful in cases where you want to enrich a command with additional information or perform advanced validation that is not covered by Json Schema. Read more about command preprocessors in the docs.
Since we don't use one in the building application, we don't really need to implement the method. Let's assume that our future command preprocessors will be simple callables:
src/System/Flavour/MyServiceMessagePort.php
/**
* @param mixed $customCommand
* @param mixed $preProcessor Custom preprocessor
* @return mixed Custom message
*/
public function callCommandPreProcessor($customCommand, $preProcessor)
{
if(is_callable($preProcessor)) {
return $preProcessor($customCommand);
}
throw new \RuntimeException("Cannot call preprocessor. Got "
. (is_object($preProcessor)? get_class($preProcessor) : gettype($preProcessor))
);
}
Instead of an aggregate a command can also be routed to a controller and the controller can decide if it forwards the command to an application service or return a list of other commands that are dispatched automatically. This is very useful for migrations or in scenarios where you want to use CQRS without event sourcing and without aggregates. In our demo application we don't use command controllers. Just like preprocessors we define them as callable.
public function callCommandController($customCommand, $controller)
{
if(is_callable($controller)) {
return $controller($customCommand);
}
throw new \RuntimeException("Cannot call command controller. Got "
. (is_object($controller)? get_class($controller) : gettype($controller))
);
}
Another concept that we don't know yet. A context provider can be used to inject context into aggregate functions. Read more about context providers in the docs (@TODO link docs).
We're implementing a functional Flavour, so we expect a callable context provider passed to the port:
src/System/Flavour/MyServiceMessagePort.php
/**
* @param mixed $customCommand
* @param mixed $contextProvider
* @return mixed
*/
public function callContextProvider($customCommand, $contextProvider)
{
if(is_callable($contextProvider)) {
return $contextProvider($customCommand);
}
throw new \RuntimeException("Cannot call context provider. Got "
. (is_object($contextProvider)? get_class($contextProvider) : gettype($contextProvider))
);
}
Last port method asks us to call a resolver. When using the PrototypingFlavour
query resolvers should implement
EventEngine\Querying\Resolver
. But this is no longer possible because we want to pass our own queries to
the resolvers and not Event Engine's generic message class. Hence, we need to define a project specific resolver interface instead
along with a query marker interface:
src/Domain/Resolver/Query.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver;
interface Query
{
//Query marker interface
}
src/Domain/Resolver/Resolver.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver;
interface Resolver
{
/**
* @param Query $query
* @return mixed array or object with toArray or JsonSerializable support
*/
public function resolve(Query $query);
}
Existing queries should implement the marker interface and resolvers should implement the new resolver interface:
src/Domain/Resolver/Query/GetBuilding.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver\Query;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Resolver\Query;
final class GetBuilding implements ImmutableRecord, Query
{
use ImmutableRecordLogic;
/**
* @var BuildingId
*/
private $buildingId;
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
}
Add the interface to GetBuildings
and GetUserBuildingList
as well.
src/Domain/Resolver/BuildingResolver.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver;
use EventEngine\DocumentStore\DocumentStore;
use EventEngine\DocumentStore\Filter\AnyFilter;
use EventEngine\DocumentStore\Filter\LikeFilter;
use MyService\Domain\Api\Payload;
use MyService\Domain\Model\Building\BuildingId;
use MyService\Domain\Resolver\Query\GetBuilding;
use MyService\Domain\Resolver\Query\GetBuildings;
final class BuildingResolver implements Resolver
{
public const COLLECTION = 'buildings';
public const STATE = 'state';
public const STATE_DOT = 'state.';
/**
* @var DocumentStore
*/
private $documentStore;
public function __construct(DocumentStore $documentStore)
{
$this->documentStore = $documentStore;
}
/**
* @param Query $query
* @return array
*/
public function resolve(Query $query): array
{
switch (true) {
case $query instanceof GetBuilding:
return $this->resolveBuilding($query->buildingId());
case $query instanceof GetBuildings:
return $this->resolveBuildings($query->name());
default:
throw new \RuntimeException("Query not supported. Got " . VariableType::determine($query));
}
}
private function resolveBuilding(BuildingId $buildingId): array
{
$buildingDoc = $this->documentStore->getDoc(self::COLLECTION, $buildingId->toString());
if(!$buildingDoc) {
throw new \RuntimeException("Building not found", 404);
}
return $buildingDoc[self::STATE];
}
private function resolveBuildings(string $nameFilter = null): array
{
$filter = $nameFilter?
new LikeFilter(self::STATE_DOT . Payload::NAME, "%$nameFilter%")
: new AnyFilter();
$cursor = $this->documentStore->findDocs(self::COLLECTION, $filter);
$buildings = [];
foreach ($cursor as $doc) {
$buildings[] = $doc[self::STATE];
}
return $buildings;
}
}
src/Domain/Resolver/UserBuildingResolver.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Resolver;
use EventEngine\DocumentStore\DocumentStore;
use EventEngine\Util\VariableType;
use MyService\Domain\Resolver\Query\GetUserBuildingList;
final class UserBuildingResolver implements Resolver
{
/**
* @var DocumentStore
*/
private $documentStore;
/**
* @var string
*/
private $userBuildingCollection;
/**
* @var string
*/
private $buildingCollection;
public function __construct(DocumentStore $documentStore, string $userBuildingCol, string $buildingCol)
{
$this->documentStore = $documentStore;
$this->userBuildingCollection = $userBuildingCol;
$this->buildingCollection = $buildingCol;
}
public function resolve(Query $query): array
{
if(!$query instanceof GetUserBuildingList) {
throw new \RuntimeException("Invalid query. Can only handle " . GetUserBuildingList::class
. '. But got ' . VariableType::determine($query));
}
$userBuilding = $this->documentStore->getDoc(
$this->userBuildingCollection,
$query->name()->toString()
);
if(!$userBuilding) {
return [
'user' => $query->name()->toString(),
'building' => null
];
}
$building = $this->documentStore->getDoc(
$this->buildingCollection,
$userBuilding['buildingId']
);
if(!$building) {
return [
'user' => $query->name()->toString(),
'building' => null
];
}
return [
'user' => $query->name()->toString(),
'building' => $building['state'],
];
}
}
src/System/Flavour/MyServiceMessagePort.php
public function callResolver($customQuery, $resolver)
{
if(! $resolver instanceof Resolver) {
throw new \RuntimeException("Unsupported resolver. Got " . VariableType::determine($resolver));
}
return $resolver->resolve($customQuery);
}
All methods of the Functional\Port
are implemented. Good job! But we're not done yet.
The flavour is configured in src/System/SystemServices.php
and can be changed there:
<?php
declare(strict_types=1);
namespace MyService\System;
use EventEngine\Data\ImmutableRecordDataConverter;
use EventEngine\Logger\LogEngine;
use EventEngine\Logger\SimpleMessageEngine;
use EventEngine\Messaging\Message;
use EventEngine\Prooph\V7\EventStore\GenericProophEvent;
use EventEngine\Runtime\Flavour;
use EventEngine\Runtime\FunctionalFlavour;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use MyService\System\Api\EventEngineConfig;
use MyService\System\Api\SystemQuery;
use MyService\System\Api\SystemType;
use MyService\System\Flavour\MyServiceMessagePort;
use Prooph\Common\Messaging\NoOpMessageConverter;
use Prooph\ServiceBus\Message\HumusAmqp\AmqpMessageProducer;
use Psr\Log\LoggerInterface;
trait SystemServices
{
/* ... */
public function flavour(): Flavour
{
return $this->makeSingleton(Flavour::class, function () {
return new FunctionalFlavour(new MyServiceMessagePort(), new ImmutableRecordDataConverter());
});
}
/* ... */
}
Everything set up 🎉. Refactoring can start!
Switching the Flavour means all generic messages have to be replaced with their concrete implementations.
In a larger project we might want to switch to another Flavour step by step. In that case a "Proxy Flavour" is required that uses PrototypingFlavour and FunctionalFlavour (or OopFlavour) internally together with a mapping of already migrated parts of the application.
src/Domain/Model/Building.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Model;
use MyService\Domain\Model\Building\Command\AddBuilding;
use MyService\Domain\Model\Building\Command\CheckInUser;
use MyService\Domain\Model\Building\Command\CheckOutUser;
use MyService\Domain\Model\Building\Event\BuildingAdded;
use MyService\Domain\Model\Building\Event\DoubleCheckInDetected;
use MyService\Domain\Model\Building\Event\DoubleCheckOutDetected;
use MyService\Domain\Model\Building\Event\UserCheckedIn;
use MyService\Domain\Model\Building\Event\UserCheckedOut;
final class Building
{
public static function add(AddBuilding $addBuilding): \Generator
{
yield BuildingAdded::fromArray($addBuilding->toArray());
}
public static function whenBuildingAdded(BuildingAdded $buildingAdded): Building\State
{
return Building\State::fromArray($buildingAdded->toArray());
}
public static function checkInUser(Building\State $state, CheckInUser $checkInUser): \Generator
{
if($state->isUserCheckedIn($checkInUser->name())) {
yield DoubleCheckInDetected::fromArray($checkInUser->toArray());
return;
}
yield UserCheckedIn::fromArray($checkInUser->toArray());
}
public static function whenUserCheckedIn(Building\State $state, UserCheckedIn $userCheckedIn): Building\State
{
return $state->withCheckedInUser($userCheckedIn->name());
}
public static function whenDoubleCheckInDetected(Building\State $state, DoubleCheckInDetected $event): Building\State
{
//No state change required, simply return current state
return $state;
}
public static function checkOutUser(Building\State $state, CheckOutUser $checkOutUser): \Generator
{
if(!$state->isUserCheckedIn($checkOutUser->name())) {
yield DoubleCheckOutDetected::fromArray($checkOutUser->toArray());
return;
}
yield UserCheckedOut::fromArray($checkOutUser->toArray());
}
public static function whenUserCheckedOut(Building\State $state, UserCheckedOut $userCheckedOut): Building\State
{
return $state->withCheckedOutUser($userCheckedOut->name());
}
public static function whenDoubleCheckOutDetected(Building\State $state, DoubleCheckOutDetected $event): Building\State
{
return $state;
}
}
Building\State
should make use of the new data types as well:
<?php
declare(strict_types=1);
namespace MyService\Domain\Model\Building;
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
final class State implements ImmutableRecord
{
use ImmutableRecordLogic;
public const BUILDING_ID = 'buildingId';
public const NAME = 'name';
/**
* @var BuildingId
*/
private $buildingId;
/**
* @var BuildingName
*/
private $name;
/**
* @var Username[]
*/
private $users = [];
private static function arrayPropItemTypeMap(): array
{
return ['users' => Username::class];
}
/**
* Called in constructor after setting props but before not null assertion
*
* Override to set default props after construction
*/
private function init(): void
{
//Build internal users map
$users = [];
foreach ($this->users as $username) {
$users[$username->toString()] = null;
}
$this->users = $users;
}
/**
* @return BuildingId
*/
public function buildingId(): BuildingId
{
return $this->buildingId;
}
/**
* @return BuildingName
*/
public function name(): BuildingName
{
return $this->name;
}
/**
* @return Username[]
*/
public function users(): array
{
return array_map(function (string $username) {
return Username::fromString($username);
}, array_keys($this->users));
}
public function withCheckedInUser(Username $username): State
{
$copy = clone $this;
$copy->users[$username->toString()] = null;
return $copy;
}
public function withCheckedOutUser(Username $username): State
{
if(!$this->isUserCheckedIn($username)) {
return $this;
}
$copy = clone $this;
unset($copy->users[$username->toString()]);
return $copy;
}
public function isUserCheckedIn(Username $username): bool
{
return array_key_exists($username->toString(), $this->users);
}
}
UserBuildingList
projector now needs to implement the interface EventEngine\Projecting\CustomEventProjector
instead of EventEngine\Projecting\Projector
:
src/Domain/Projector/UserBuildingList.php
<?php
declare(strict_types=1);
namespace MyService\Domain\Projector;
use EventEngine\DocumentStore\DocumentStore;
use EventEngine\Projecting\CustomEventProjector;
use MyService\Domain\Api\Payload;
use EventEngine\Projecting\AggregateProjector;
use MyService\Domain\Model\Building\Event\UserCheckedIn;
use MyService\Domain\Model\Building\Event\UserCheckedOut;
final class UserBuildingList implements CustomEventProjector
{
/**
* @var DocumentStore
*/
private $documentStore;
public function __construct(DocumentStore $documentStore)
{
$this->documentStore = $documentStore;
}
public function prepareForRun(string $projectionVersion, string $projectionName): void
{
if(!$this->documentStore->hasCollection(self::generateCollectionName($projectionVersion, $projectionName))) {
$this->documentStore->addCollection(
self::generateCollectionName($projectionVersion, $projectionName)
/* Note: we could pass index configuration as a second argument, see docs for details */
);
}
}
public function handle(string $appVersion, string $projectionName, $event): void
{
$collection = $this->generateCollectionName($appVersion, $projectionName);
switch (true) {
case $event instanceof UserCheckedIn:
$this->documentStore->addDoc(
$collection,
$event->name()->toString(), //Use username as doc id
[Payload::BUILDING_ID => $event->buildingId()->toString()]
);
break;
case $event instanceof UserCheckedOut:
$this->documentStore->deleteDoc($collection, $event->name()->toString());
break;
default:
//Ignore unknown events
}
}
public function deleteReadModel(string $appVersion, string $projectionName): void
{
$this->documentStore->dropCollection(self::generateCollectionName($appVersion, $projectionName));
}
public static function generateCollectionName(string $projectionVersion, string $projectionName): string
{
//We can use the naming strategy of the aggregate projector for our custom projection
return AggregateProjector::generateCollectionName($projectionVersion, $projectionName);
}
}
The UiExchange
event listener included in the skeleton application needs to be aligned, too.
First, the corresponding interface should handle any type of event:
src/System/UiExchange.php
<?php
declare(strict_types=1);
namespace MyService\System;
interface UiExchange
{
public function __invoke($event): void;
}
Second, an implementation of the interface should handle our event objects. The skeleton simply uses an anonymous class
to implement the interface. It can be found and changed in src/System/SystemServices.php
.
It's an anonymous class because the UiExchange is only included in the skeleton to demonstrate how events can be pushed to a message queue and consumed by a UI. The implementation is not meant to be used in production. You can get some inspiration from it, but please work out a production grade solution yourself.
<?php
declare(strict_types=1);
namespace MyService\System;
use EventEngine\Data\ImmutableRecordDataConverter;
use EventEngine\Logger\LogEngine;
use EventEngine\Logger\SimpleMessageEngine;
use EventEngine\Messaging\MessageBag;
use EventEngine\Prooph\V7\EventStore\GenericProophEvent;
use EventEngine\Runtime\Flavour;
use EventEngine\Runtime\FunctionalFlavour;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use MyService\Domain\Api\Event;
use MyService\System\Api\EventEngineConfig;
use MyService\System\Api\SystemQuery;
use MyService\System\Api\SystemType;
use MyService\System\Flavour\MyServiceMessagePort;
use Prooph\Common\Messaging\NoOpMessageConverter;
use Prooph\ServiceBus\Message\HumusAmqp\AmqpMessageProducer;
use Psr\Log\LoggerInterface;
trait SystemServices
{
/* ... */
public function uiExchange(): UiExchange
{
return $this->makeSingleton(UiExchange::class, function () {
$this->assertMandatoryConfigExists('rabbit.connection');
$connection = new \Humus\Amqp\Driver\AmqpExtension\Connection(
$this->config()->arrayValue('rabbit.connection')
);
$connection->connect();
$channel = $connection->newChannel();
$exchange = $channel->newExchange();
$exchange->setName($this->config()->stringValue('rabbit.ui_exchange', 'ui-exchange'));
$exchange->setType('fanout');
$humusProducer = new \Humus\Amqp\JsonProducer($exchange);
$messageProducer = new \Prooph\ServiceBus\Message\HumusAmqp\AmqpMessageProducer(
$humusProducer,
new NoOpMessageConverter()
);
$flavour = $this->flavour();
return new class($messageProducer, $flavour) implements UiExchange {
private $producer;
private $flavour;
public function __construct(AmqpMessageProducer $messageProducer, Flavour $flavour)
{
$this->producer = $messageProducer;
$this->flavour = $flavour;
}
public function __invoke($event): void
{
$messageBag = new MessageBag(
Event::nameOf($event),
MessageBag::TYPE_EVENT,
$event
);
$event = $this->flavour->prepareNetworkTransmission($messageBag);
$this->producer->__invoke(GenericProophEvent::fromArray([
'uuid' => $event->uuid()->toString(),
'message_name' => $event->messageName(),
'payload' => $event->payload(),
'metadata' => $event->metadata(),
'created_at' => $event->createdAt()
]));
}
};
});
}
}
That's it! You can use Cockpit to test the changes.
Or wait! We did not run the tests!
docker-compose run php php vendor/bin/phpunit
Doesn't look good, right? Let's fix it!
TestCaseAbstract::assertRecordedEvent()
method need to be aligned:
<?php
declare(strict_types=1);
namespace MyServiceTest;
use EventEngine\DocumentStore\DocumentStore;
use EventEngine\EventEngine;
use EventEngine\EventStore\EventStore;
use EventEngine\Logger\DevNull;
use EventEngine\Logger\SimpleMessageEngine;
use EventEngine\Messaging\Message;
use EventEngine\Messaging\MessageProducer;
use EventEngine\Persistence\InMemoryConnection;
use EventEngine\Prooph\V7\EventStore\InMemoryMultiModelStore;
use EventEngine\Util\MessageTuple;
use MyService\Domain\Api\Event;
use MyService\ServiceFactory;
use MyServiceTest\Mock\EventQueueMock;
use MyServiceTest\Mock\MockContainer;
use PHPUnit\Framework\TestCase;
class TestCaseAbstract extends TestCase
{
/* ... */
protected function assertRecordedEvent(string $eventName, array $payload, array $events, $assertNotRecorded = false): void
{
$isRecorded = false;
foreach ($events as $evt) {
if($evt === null) {
continue;
}
//Convert domain events to raw data
$evtName = Event::nameOf($evt);
$evtPayload = $evt->toArray();
if($eventName === $evtName) {
$isRecorded = true;
if(!$assertNotRecorded) {
$this->assertEquals($payload, $evtPayload, "Payload of recorded event $evtName does not match with expected payload.");
}
}
}
if($assertNotRecorded) {
$this->assertFalse($isRecorded, "Event $eventName is recorded");
} else {
$this->assertTrue($isRecorded, "Event $eventName is not recorded");
}
}
}
NotifySecurityTest
contains a mocked UiExchange
. We changed the interface earlier, but did not change the mock.
The test itself needs minor adjustments, too.
tests/Integration/NotifySecurityTest.php
<?php
declare(strict_types=1);
namespace MyServiceTest\Integration;
use MyService\Domain\Api\Command;
use MyService\Domain\Api\Event;
use MyService\Domain\Api\Payload;
use MyService\Domain\Model\Building\Event\DoubleCheckInDetected;
use MyService\System\UiExchange;
use MyServiceTest\IntegrationTestCase;
final class NotifySecurityTest extends IntegrationTestCase
{
const BUILDING_ID = '7c5f0c8a-54f2-4969-9596-b5bddc1e9421';
const BUILDING_NAME = 'Acme Headquarters';
const USERNAME = 'John';
private $uiExchange;
protected function setUp(): void
{
parent::setUp();
//Mock UiExchange with an anonymous class that keeps track of the last received message
$this->uiExchange = new class implements UiExchange {
private $lastReceivedMessage;
public function __invoke($event): void
{
$this->lastReceivedMessage = $event;
}
public function lastReceivedMessage()
{
return $this->lastReceivedMessage;
}
};
// Mocks are passed to EE set up method
// The IntegrationTestCase loads all EE descriptions
// and uses the configured Flavour (PrototypingFlavour in our case)
// to set up Event Engine
$this->setUpEventEngine([
UiExchange::class => $this->uiExchange,
]);
/**
* We can pass fixtures to the database set up:
*
* Stream to events map:
*
* [streamName => Event[]]
*
* Collection to documents map:
*
* [collectionName => [docId => doc]]
*/
$this->setUpDatabase([
// We use the default write model stream in the buildings app
// and add a history for the test building
// aggregate state is derived from history automatically during set up
$this->eventEngine->writeModelStreamName() => [
$this->makeEvent(Event::BUILDING_ADDED, [
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::BUILDING_NAME
]),
$this->makeEvent(Event::USER_CHECKED_IN, [
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME
]),
]
]);
}
/**
* @test
*/
public function it_detects_double_check_in_and_notifies_security()
{
//Try to check in John twice
$checkInJohn = $this->makeCommand(Command::CHECK_IN_USER, [
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME
]);
$this->eventEngine->dispatch($checkInJohn);
//The IntegrationTestCase sets up an in-memory queue (accessible by $this->eventQueue)
//You can inspect published events or simply process the queue
//so that event listeners get invoked like our mocked UiExchange listener
$this->processEventQueueWhileNotEmpty();
//Now $this->lastPublishedEvent should point to the event received by UiExchange mock
$this->assertNotNull($this->uiExchange->lastReceivedMessage());
$this->assertInstanceOf(DoubleCheckInDetected::class, $this->uiExchange->lastReceivedMessage());
$this->assertEquals([
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME
], $this->uiExchange->lastReceivedMessage()->toArray());
}
}
Next on the list is BuildingTest
.
tests/Domain/Model/BuildingTest.php
<?php
declare(strict_types=1);
namespace MyServiceTest\Domain\Model;
use MyService\Domain\Api\Command;
use MyService\Domain\Api\Event;
use MyService\Domain\Api\Payload;
use MyServiceTest\UnitTestCase;
use Ramsey\Uuid\Uuid;
use MyService\Domain\Model\Building;
final class BuildingTest extends UnitTestCase
{
private $buildingId;
private $buildingName;
private $username;
protected function setUp(): void
{
$this->buildingId = Uuid::uuid4()->toString();
$this->buildingName = 'Acme Headquarters';
$this->username = 'John';
parent::setUp();
}
/**
* @test
*/
public function it_checks_in_a_user()
{
//Prepare expected aggregate state
$state = Building\State::fromArray([
Building\State::BUILDING_ID => $this->buildingId,
Building\State::NAME => $this->buildingName
]);
//Use test helper UnitTestCase::makeCommand() to construct command
$command = Building\Command\CheckInUser::fromArray([
Building\State::BUILDING_ID => $this->buildingId,
Building\State::NAME => $this->username,
]);
//Aggregate functions yield events, we have to collect them with a test helper
$events = $this->collectNewEvents(
Building::checkInUser($state, $command)
);
//Another test helper to assert that list of recorded events contains given event
$this->assertRecordedEvent(Event::USER_CHECKED_IN, [
Payload::BUILDING_ID => $this->buildingId,
Payload::NAME => $this->username
], $events);
}
}
And last adjustments in UserBuildingListTest::it_manages_list_of_users_with_building_reference()
.
The projector expects dedicated event objects now.
tests/Domain/Projector/UserBuildingListTest.php
<?php
declare(strict_types=1);
namespace MyServiceTest\Domain\Projector;
use EventEngine\DocumentStore\Filter\AnyFilter;
use MyService\Domain\Api\Event;
use MyService\Domain\Api\Payload;
use MyService\Domain\Api\Projection;
use MyService\Domain\Model\Building\Event\UserCheckedIn;
use MyService\Domain\Model\Building\Event\UserCheckedOut;
use MyService\Domain\Projector\UserBuildingList;
use MyServiceTest\UnitTestCase;
final class UserBuildingListTest extends UnitTestCase
{
const PRJ_VERSION = '0.1.0';
const BUILDING_ID = '7c5f0c8a-54f2-4969-9596-b5bddc1e9421';
const USERNAME1 = 'John';
const USERNAME2 = 'Jane';
/**
* @var UserBuildingList
*/
private $projector;
protected function setUp(): void
{
parent::setUp();
//DocumentStore is set up in parent::setUp()
$this->projector = new UserBuildingList($this->documentStore);
$this->projector->prepareForRun(
self::PRJ_VERSION,
Projection::USER_BUILDING_LIST
);
}
/**
* @test
*/
public function it_manages_list_of_users_with_building_reference()
{
$collection = UserBuildingList::generateCollectionName(
self::PRJ_VERSION,
Projection::USER_BUILDING_LIST
);
$johnCheckedIn = UserCheckedIn::fromArray([
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME1
]);
$this->projector->handle(
self::PRJ_VERSION,
Projection::USER_BUILDING_LIST,
$johnCheckedIn
);
$users = iterator_to_array($this->documentStore->findDocs(
$collection,
new AnyFilter()
));
$this->assertEquals($users, [
'John' => ['buildingId' => self::BUILDING_ID]
]);
$janeCheckedIn = UserCheckedIn::fromArray([
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME2
]);
$this->projector->handle(
self::PRJ_VERSION,
Projection::USER_BUILDING_LIST,
$janeCheckedIn
);
$users = iterator_to_array($this->documentStore->findDocs(
$collection,
new AnyFilter()
));
$this->assertEquals($users, [
'John' => ['buildingId' => self::BUILDING_ID],
'Jane' => ['buildingId' => self::BUILDING_ID],
]);
$johnCheckedOut = UserCheckedOut::fromArray([
Payload::BUILDING_ID => self::BUILDING_ID,
Payload::NAME => self::USERNAME1
]);
$this->projector->handle(
self::PRJ_VERSION,
Projection::USER_BUILDING_LIST,
$johnCheckedOut
);
$users = iterator_to_array($this->documentStore->findDocs(
$collection,
new AnyFilter()
));
$this->assertEquals($users, [
'Jane' => ['buildingId' => self::BUILDING_ID],
]);
}
}
Tests are green again. Refactoring finished successfully. Was it worth the effort? Switching the Flavour is quite some work to do, isn't it? Depending on the amount of already written code and tests this task can take some days and you need to make sure that you don't break existing functionality. On the other hand you get a fully decoupled domain model. Of course, it's also possible to use another Flavour right from the beginning. But keep in mind, that the PrototypingFlavour saves a lot of time in the early days of a project. You don't know if the first app version really meets business and user needs. You can only try and experiment. The faster you have a working app, the faster you can get feedback from users and stakeholders. A lean implementation and simple infrastructure gives you a lot of flexibility at the beginning. Starting with a MVP is not a new concept. Event Engine just gives you a nice tool to build one and reuse parts of your experiments in later project phases. Also using CQRS / ES from day one gives you full advantage of a reactive system.
Still curious to see what the OopFlavour can do? The last bonus part sheds light on it.


How To Write A Data Definition Language from a selected file:
This guide will walk you through taking an Edge file, loading it into the Data Definition Language(DDL) program,
selecting a database product to construct a new DDL with, and finally writing the new DDL to a new file.
In order to write a new DDL, ensure you have available: a source Edge (.edg) file and a Database Product Conversion Class file (.class)
First, we will open an Edge file we would like to convert. Select "Open Edge File" from the "File" menu and naviagte to your desired Edge file(.edg).
Next, we will be setting our DDL language conversion file location. Navigate to the "Options" menu and select "Set Output File Definition Location".
From the file explorer, navigate to the directory which contains the class file(.class) of your Data Definition Language converter. this file will be located in a directory such as
"projectpath/build/classes/java/main/". In our case we will be targeting the file "CreateDDLMySQL.class" which is included standard with the software.
After selecting a class file a message will appear letting you know which DDL languages are available.
If you wish to verify your loaded DDL languages, please see our Guide to viewing avaialable DDL products.
Next, define your desired table fields along with any desired table relations under the "Define Relations" and "Define Tables" menus.
Once tables and relations have been defined, click the "Create DDL" button to begin the process of converting your Edge file to your desired DDL.
A small dropdown menu will appear asking you to select your desired language to convert to. We will be using MySQL in this example.
Next enter the name of your new database. Then select the desired location for saving the new DDL file.
Once saved, you will now have a new file that can be used to establish a databse in your system of choice! To verify all went well, open the file you just created,
in our case "MySQLDB.sql".
Repeat this process for as many Edge files as needed!


Days
Hours
Minutes
Seconds
How Diablo was completely Reverse Engineered without Source Code
Software development consultant at Sparkhound in Baton Rouge, LA.
Received M.S. in Computer Engineering from LSU.
Interested in all things Angular, .NET, and maybe Rust.
Content Management Systems Rule!
Easy to modify content
Distributed ownership
Reuseable across projects
Content Management Systems Are Bad, and they should feel bad!
Inefficient developer experience.
Bad UI/UX!
(editing is slow or buggy, content gets jumbled on save, styles overwrite the editor, etc...)
Monolithic (more features than needed)
Limited backend choices (PHP)
Do not support new trends well like SPA Frameworks, Mobile, etc.
Wordpress
Drupal
Kentico
Sitefinity
Wix
Squarespace
...and many more!
Much better content management ...
... but they gave up on the UI features!
AirTable, Coda, Notion, etc...
https://www.slashgear.com/conquer-2019-with-these-new-breed-of-productivity-tools-01559572/
Components!
Dynamic Creation
Visual Layout/Content Editing
Bonus: Lazy Loading and Plugins
What is a component?
How do we create them?
How do we share them?
Also called Blocks, Elements, or Widgets for old folks
Inputs
Outputs
Services (Dependency Injection)
Content Projection
<input type="text" value="initial value">
<button onclick="console.log(event);">Click Me</button>
@Component({
selector: 'container',
templateUrl: './container.component.html',
styleUrls: ['./container.component.scss'],
})
export class ContainerComponent {
constructor(private sanitizer: DomSanitizer) {
}
}

<button>Click Me</button>

<my-custom-component>

Title

<button>Click Me</button>
</my-custom-component>
What is a component?
How do we create them?
How do we share them?
@Component({
selector: 'container',
template: `

{{someInput}}

<button (click)="someInputChange($event)">Click Me</button>
<ng-content select="slot-name"></ng-content>
`,
styleUrls: ['./container.component.scss'],
})
export class ContainerComponent {
constructor(private sanitizer: DomSanitizer) {
}
@Input() someInput: string;
@Output() someInputChange = new EventEmitter<string>();
}
What is a component?
How do we create them?
How do we share them?
Angular CLI makes it easy to create a Library!
> ng generate library my-lib
> ng build my-lib --watch
> ng serve
Works, but I ran into occasional file lock issues...
Configure your tsconfig.json file to reference the library code directly!
{
"compilerOptions": {
"paths": {
"elemental": [
"projects/elemental/src/public-api"
],
"elemental/*": [
"projects/elemental/src/public-api/*"
],
// ....
}
}
// ....
}
Framework Independent (works with Angular, React, Vue, plain JavaScript, etc.)
What is a component?
How do we create them?
How do we share them?
Components!
Dynamic Creation
Visual Layout/Content Editing
Bonus: Lazy Loading and Plugins
<ng-container #host></ng-container>
export class SomeComponent {
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
@ViewChild('host', {static: true, read: ViewContainerRef }) host: ViewContainerRef;
loadComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(MyComponent);
const viewContainerRef = this.host.viewContainerRef;
const componentRef = viewContainerRef.createComponent(componentFactory);
// You can set Input/Output properties!
(<MyComponent>componentRef.instance).property = property;
}
}
entryComponents: [
HeroJobAdComponent,
HeroProfileComponent
],
schema: ElementalBootstrapSchema = {
type: "container",
props: { fluid: true }
};
export let elementalTypes = {
"container": ContainerComponent,
"row": RowComponent,
"input": InputComponent,
"input-text": InputTextComponent,
"input-checkbox": InputCheckboxComponent,
"input-file": InputFileComponent,
};
<elemental [schema]="schema"></elemental>
schema: ElementalBootstrapSchema = {
type: "container",
props: { fluid: true },
children: [
{
type: "row",
props: {},
children: [
{
type: "input",
props: {}
},
]
}
]
};
export type ElementalBootstrapSchema =
{
type: "container",
props: ContainerComponentData,
children?: ElementalBootstrapSchema[],
} | {
type: "row",
props: RowComponentData,
children?: ElementalBootstrapSchema[],
} | {
type: "input-text",
props: InputTextComponentData,
} | {
type: "input-checkbox",
props: InputCheckboxComponentData,
};
CMS
Dashboards
Lazy Loading
Code Generation
Create a schema by hand.
Template-driven vs Reactive
Custom Controls? Nested? Projected?
Collecting results into a model
<formly-form
[model]="model"
[fields]="fields"
[options]="options"
[form]="form">
</formly-form>
fields: FormlyFieldConfig[] = [
{
key: 'text',
type: 'input',
templateOptions: {
label: 'Text',
placeholder: 'Formly is terrific!',
required: true,
},
},
];
Interface for custom controls
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}
Explains how to handle validation as well!
Allows us to use the dynamic componet just like any other control
<elemental [schema]="schema" [(ngModel)]="model"></elemental>
Components!
Dynamic Creation
Visual Layout/Content Editing
Bonus: Lazy Loading and Plugins
Many users would like to move things around visually!
End-users
Designers
Grandma
... and even developers!
Wordpress (Gutenberg, Bakery, etc...)
Dragon-drop?
Angular CDK drag-and-drop
Components!
Dynamic Creation
Visual Layout/Content Editing
Bonus: Lazy Loading and Plugins
> ng build --project elemental-bootstrap-bundle --prod --output-hashing none --single-bundle true --extraWebpackConfig webpack.extra.js
module.exports = {
output: {
library: 'elemental-bundle',
libraryTarget: 'umd'
},
"externals": {
"rxjs": "rxjs",
"@angular/core": "ng.core",
"@angular/common": "ng.common",
"@angular/platform-browser": "ng.platformBrowser",
"@angular/platform-browser-dynamic": "ng.platformBrowserDynamic",
"@angular/elements": "ng.elements"
}
}
export * from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module';
// NOTE:
// Dont worry if Angular Language Service shows errors when importing/exporting ngfactory.
// It should still compile correctly.
export * from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module.ngfactory';
import { ElementalBootstrapModuleNgFactory } from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module.ngfactory';
export default ElementalBootstrapModuleNgFactory;
let exports = {};
let module = {exports: exports};
let modules = {
'ng.core': core,
'ng.common': common,
'ng.cdk': cdk,
'ng.platformBrowser': platformBrowser,
'ng.platformBrowserDynamic': platformBrowserDynamic,
'ngxLogger': ngxLoader,
"rxjs": rxjs,
};
let url = `http://localhost:4200/${dynamicModule.url}`;
let response = await this.http.get(url, { responseType: "text" }).toPromise();
// shim 'require' and eval
let require: any = (module) => modules[module];
let func = Function("exports, module, require", response);
let result = func(exports, module, require);
let plugin = exports['ElementalBootstrapModuleFactory'];
let module = plugin.ngModuleFactory.create(this.injector);
let component = plugin.componentFactories[0].create(this.injector, null, null, module);
Components!
Dynamic Creation
Visual Layout/Content Editing
Bonus: Lazy Loading and Plugins
Components Rule
They can be Dynamic
Make your own CMS
Your feedback is valuable!
Created by John Harvey


{% extends "layout-v2.html" %} {% block pageScripts %} {% endblock %} {% block pageTitle %} NHS Cervical screening management system {% endblock %} {% block header %} {% include '../components/header.html' %} {% endblock %} {% block content %}
If you cannot find the patient you're looking for refine the search.
| Transfer date | Name | NHS number | New location and district | Action | Status |
|---|---|---|---|---|---|
| 10-Sep-21 | Lucy Harper |<PHONE_NUMBER> | Grampian, Scotland (N) | Transfer completedCRM number: CAS-12345-ABCDE | |
| 18-Nov-21 | Ella Depetris |<PHONE_NUMBER> | Fife, Scotland (F) | Transfer completedCRM number: CAS-12345-ABCDE | |
| 18-Nov-21 | Hannah Rowley |<PHONE_NUMBER> | Northern Ireland | Transfer patient | Transfer not complete |
| 18-Nov-21 | Mary Homes |<PHONE_NUMBER> | Lothian, Scotland (S) | Transfer patient | Transfer not complete |


Kia ora, e te whānau! I'm Rachael, a twenty-something aspiring-Web Developer, yogi and nature-lover.
Welcome to my blog! This is a place where I share mindfulness musings, technical tidbits and generally share my journey through Enspiral Dev Academy's Phase 0.


Created specifically for clinical reporting by manual curation
- Include a minimum set of transcripts for reporting at a locus (ideally one)
- Manually curated by expert scientists
Stable
- Include transcript sequences that are stable and independent of changes to transcript models (RefSeq and GENCODE)
- Establish a coordinate system that is independent from upgrades to the reference genome assembly, and provides mappings to present and past assemblies
- Use a unique and stable identifier that is not versioned
Flexible and collaborative
- Allow inclusion of legacy or community-requested reference sequences
- Individual records created at the request of the clinical community and in collaboration with gene-specific experts
Connect the past, present and future of clinical variant reporting
- Part of the effort to rationalise differences in NCBI (RefSeq) and EMBL-EBI (Ensembl/GENCODE) gene sets
- Aim to achieve faster convergence between NCBI (RefSeq) and EMBL-EBI (Ensembl/GENCODE) on key high value annotations to provide a common minimal set of transcripts per gene
- Facilitate unambiguous multi-directional data exchange between NCBI (RefSeq), EMBL-EBI (Ensembl/GENCODE) and the reference genome assemblies (GRCh37, GRCh38)
- Define the relationship between legacy and community-requested sequences and the reference assembly
- Ensure compatibility with genome reference assembly-based NGS variant reporting systems
Well-supported
- Integrated into the Ensembl, NCBI and UCSC genome browsers to allow visualization in genomic context, with all other existing annotations
- Compatible with the Human Genome Variation Society (HGVS) nomenclature and supported by nomenclature checker systems (e.g. Mutalyzer, VariantValidator)
- Generated and maintained by the NCBI and EMBL-EBI


Interface SaslServerListener
-
public interface SaslServerListener
Listener for SASL frame arrival to facilitate relevant handling for the SASL negotiation of the server side of the SASL exchange. See the AMQP specification SASL negotiation process overview for related detail.
-
-
Method Summary
All Methods Instance Methods Abstract Methods Default Methods Modifier and Type Method Description void
handleSaslHeader(SaslServerContext context, AMQPHeader header)
Called when the SASL header has been received and the server is now ready to send the configured SASL mechanisms.void
handleSaslInit(SaslServerContext context, Symbol mechanism, ProtonBuffer initResponse)
Called when a SASL init frame has arrived from the client indicating the chosen SASL mechanism and the initial response data if any.void
handleSaslResponse(SaslServerContext context, ProtonBuffer response)
Called when a SASL response frame has arrived from the client.default void
initialize(SaslServerContext context)
Called to give the application code a clear point to initialize all the Server side expectations.
-
-
-
Method Detail
-
initialize
default void initialize(SaslServerContext context)
Called to give the application code a clear point to initialize all the Server side expectations.The application should use this event to configure the server mechanisms and other server authentication properties.
In the event that the server implementation cannot proceed with SASL authentication it should call the
SaslServerContext.saslFailure(javax.security.sasl.SaslException)
signal theEngine
that it should transition to a failed state.- Parameters:
context
- theSaslServerContext
used to authenticate the connection.
-
handleSaslHeader
void handleSaslHeader(SaslServerContext context, AMQPHeader header)
Called when the SASL header has been received and the server is now ready to send the configured SASL mechanisms. The handler should respond be calling theSaslServerContext.sendMechanisms(Symbol[])
method immediately or later using the same thread that invoked this event handler.In the event that the server implementation cannot proceed with SASL authentication it should call the
SaslServerContext.saslFailure(javax.security.sasl.SaslException)
to fail the SASL negotiation and signal theEngine
that it should transition to a failed state.- Parameters:
context
- theSaslServerContext
object that received the SASL header.header
- the AMQP Header that was read.- See Also:
SaslServerContext.sendMechanisms(Symbol[])
,SaslServerContext.saslFailure(javax.security.sasl.SaslException)
-
handleSaslInit
void handleSaslInit(SaslServerContext context, Symbol mechanism, ProtonBuffer initResponse)
Called when a SASL init frame has arrived from the client indicating the chosen SASL mechanism and the initial response data if any. Based on the chosen mechanism the server handler should provide additional challenges or complete the SASL negotiation by sending an outcome to the client. The handler can either respond immediately or it should response using the same thread that invoked this handler.In the event that the server implementation cannot proceed with SASL authentication it should call the
SaslServerContext.saslFailure(javax.security.sasl.SaslException)
to fail the SASL negotiation and signal theEngine
that it should transition to a failed state.- Parameters:
context
- theSaslServerContext
object that is to process the SASL initial frame.mechanism
- the SASL mechanism that the client side has chosen for negotiations.initResponse
- the initial response sent by the remote.- See Also:
SaslServerContext.sendChallenge(ProtonBuffer)
,SaslServerContext.sendOutcome(SaslOutcome, ProtonBuffer)
,SaslServerContext.saslFailure(javax.security.sasl.SaslException)
-
handleSaslResponse
void handleSaslResponse(SaslServerContext context, ProtonBuffer response)
Called when a SASL response frame has arrived from the client. The server should process the response and either offer additional challenges or complete the SASL negotiations based on the mechanics of the chosen SASL mechanism. The server handler should either respond immediately or should respond from the same thread that the response handler was invoked from.In the event that the server implementation cannot proceed with SASL authentication it should call the
SaslServerContext.saslFailure(javax.security.sasl.SaslException)
to fail the SASL negotiation and signal theEngine
that it should transition to a failed state.- Parameters:
context
- theSaslServerContext
object that is to process the incoming response.response
- the response sent by the remote SASL "client".- See Also:
SaslServerContext.sendChallenge(ProtonBuffer)
,SaslServerContext.sendOutcome(SaslOutcome, ProtonBuffer)
,SaslServerContext.saslFailure(javax.security.sasl.SaslException)
-
-


cocoa drought yield ENSO production trees tree Cocoa water climate droughts mortality soil data events study ENSO-related effect {border-style rainfall Bahia px;} #ffffff;} Brazil April Barro effects farms loss Change change shade strong Available: Climate average Table caused number Brazilian Fig area high increase pods recorded Bahia, affected agroforestry based log(PerDeadTree)~SoilWater natural pod severe Niño PET Sciences YieldLoss~longitude additional compared drought. extreme frequency global harvest holding using (DOCX) Click Clim GroundCover LINK November Preto SumShadeTreeDBH University cacao file. increased state Glob Indonesia NumberShadeTree Oscillation West agroforests capacity cause days disease farm months px; rate systems traditional tropical used years {font-family > .°C Biol. However, calculated clones cover decrease decreased font-size food forest grown infection large line main rain reduced reduction region research selected species vegetation America Change. Environmental Index MARS Preto, SST Soil South changes climatic crops double; drought, dry event farms. fungal future including losses model on-farm potential represents resilience showed sum temperature transects weather yields ./gcb. ./s--- Agric Agriculture August Bahia. Biology Brazil, Ecology Forest Life Para SE) Sulawesi, Syst. Water [], border-color border-width cocoa, counted drought-tolerant field major measured municipality native preceding responsible roots sensitive solid; stronger studies values {font-style {font-weight (April (Theobroma Agroforests Atlantic Brazil. Cambridge Cambridge, Conceptualization Drought Effects February Gliricidia Global Hölscher Int Köhler MCCS New Niño-Southern Plant Potential Rainfall SPEI Sci. September Thus, Tree United Writing Yield analysis annual areas associated beans bold courier; distribution em; end experiment farmers farmers’ harvests impact indirect intensity likely limited loss. lower mainly mass minimum period perniciosa plantations planted producing production. pt;} reduce reducing relationship representative result resulted review scale semi-natural shaded trees, use usually year


Hello world~
| EMOTION | SCORE (%) |
|---|---|
| ANGER | |
| CONTEMPT | |
| DISGUST | |
| FEAR | |
| HAPPINESS | |
| NEUTRAL | |
| SADNESS | |
| SURPRISE |
Wow - this hashtag is angry!
This hashtag doesn't like what it sees... hmph!
Yuck. This hashtag seems to be pretty disgusted!
Spooky! This hashtag is primariy associated with fear!
How lovely! Individuals in these photos are primarily happy.
Well, that's kind of boring. Most people in these photos are feeling pretty neutral.
Oh no, these aren't happy photos at all! What a sad hashtag.
Shocking! This hashtag is mostly associated with surprise.


|
Trinamic TMC26X Stepper Driver for Arduino
|
Go to the source code of this file.
Classes | |
| class | TMC26XStepper |
| Class representing a TMC26X stepper driver. More... | |
Defines | |
| #define | TMC26X_OVERTEMPERATURE_PREWARING 1 |
| return value for TMC26XStepper.getOverTemperature() if there is a overtemperature situation in the TMC chip | |
| #define | TMC26X_OVERTEMPERATURE_SHUTDOWN 2 |
| return value for TMC26XStepper.getOverTemperature() if there is a overtemperature shutdown in the TMC chip | |
| #define | TMC26X_READOUT_POSITION 0 |
| #define | TMC26X_READOUT_STALLGUARD 1 |
| #define | TMC26X_READOUT_CURRENT 3 |
| #define | COOL_STEP_HALF_CS_LIMIT 0 |
| #define | COOL_STEP_QUARTDER_CS_LIMIT 1 |
| #define COOL_STEP_HALF_CS_LIMIT 0 |
Define to set the minimum current for CoolStep operation to 1/2 of the selected CS minium.
Definition at line 70 of file TMC26XStepper.h.
| #define COOL_STEP_QUARTDER_CS_LIMIT 1 |
Define to set the minimum current for CoolStep operation to 1/4 of the selected CS minium.
Definition at line 75 of file TMC26XStepper.h.
| #define TMC26X_OVERTEMPERATURE_PREWARING 1 |
return value for TMC26XStepper.getOverTemperature() if there is a overtemperature situation in the TMC chip
This warning indicates that the TCM chip is too warm. It is still working but some parameters may be inferior. You should do something against it.
Definition at line 39 of file TMC26XStepper.h.
| #define TMC26X_OVERTEMPERATURE_SHUTDOWN 2 |
return value for TMC26XStepper.getOverTemperature() if there is a overtemperature shutdown in the TMC chip
This warning indicates that the TCM chip is too warm to operate and has shut down to prevent damage. It will stop working until it cools down again. If you encouter this situation you must do something against it. Like reducing the current or improving the PCB layout and/or heat management.
Definition at line 47 of file TMC26XStepper.h.
| #define TMC26X_READOUT_CURRENT 3 |
Selects to read out the current current setting (acc. to CoolStep) and the upper bits of the StallGuard value from the motor.
Definition at line 64 of file TMC26XStepper.h.
| #define TMC26X_READOUT_POSITION 0 |
Selects to readout the microstep position from the motor.
Definition at line 54 of file TMC26XStepper.h.
| #define TMC26X_READOUT_STALLGUARD 1 |
Selects to read out the StallGuard value of the motor.
Definition at line 59 of file TMC26XStepper.h.


In order to solve the linear algebraic equations (34) for hj, multiply both sides of each equation by exp, where J is an integer between [0,N − 1], and then add all the equations together, which yields
|
| (49) |
Interchanging the sequence of the two summation on the right-hand side, equation (49) is written
|
| (50) |
Using the fact that (veriﬁed by Wolfram Mathematica)
|
| (51) |
where δjJ is the Kroneker Delta, equation (50) is written
|
| (52) |
i.e.,
|
| (53) |
which can be solved to give
|
| (54) |
Equation (54) is the inverse DFT.


The meat of the Servant tutorial starts with an imposing list of language extensions and imports and only gets more confusing from there. I don't think this gives newbies (i.e. me) the best first impression of Servant. Let's build the simplest possible key-value store with it instead.
We're going to write this as a stack
script so everything is in one file.
#!/usr/bin/env stack
Let's import the modules we need.
{- stack --resolver lts-7 --install-ghc runghc
--package aeson
--package servant-server
--package text
--package transformers
--package unordered-containers
--package warp
-}
At minimum we need servant-server
for Servant goodness and warp
to actually
run our web service. The plan is to create an IORef
holding a HashMap
and
use that as our store, which is why we need unordered-containers
. I'd like to
store arbitrary JSON, therefore aeson
, and I think our keys should be Text
,
because aeson
and Text
are great together. That leaves transformers
,
which we need because of liftIO
.
It turns out that we only need two language extensions for this example.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
As far as I can tell, both these extensions are needed for Servant's cute API specification EDSL. We'll let you have this one, Servant.
Time for a (hopefully manageable) list of imports!
module Main where
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (Value)
import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef')
import Data.HashMap.Strict (HashMap, lookup, insert, empty)
import Data.Text (Text)
import Network.Wai.Handler.Warp (run)
import System.Environment (getArgs)
import Prelude hiding (lookup)
import Servant
All imports are explicit except Servant
's.
Speaking of the EDSL:
type API
= "get" :> Capture "key" Text :> Get '[JSON] (Maybe Value)
:<|> "put" :> Capture "key" Text
:> ReqBody '[JSON] Value :> Put '[JSON] Text
This API has two endpoints: a "/get/:key" endpoint that provides the value associated with a key if the key exists in our store, or a "put/:key" endpoint that allows us to associate some JSON with a key, returning the key used. How this fits together is still a bit magical to me, but this blog post provides the best explanation I've read so far. The section of the Servant tutorial on the API specification EDSL is also quite good.
Let's define a type synonym so we don't have to keep writing IORef (HashMap
Text Value)
over and over again:
type Store = IORef (HashMap Text Value)
Servant uses the same operator to define the type and the serving action:
server :: Store -> Server API
server store = getValue store :<|> putValue store
The order in which these actions are composed needs to match the order used in the API definition.
Next we define getValue
and putValue
. We need actions of type Handler
,
which is ExceptT ServantErr IO
, so we liftIO
as necessary:
getValue :: Store -> Text -> Handler (Maybe Value)
getValue store key = liftIO $ lookup key <$> readIORef store
putValue :: Store -> Text -> Value -> Handler Text
putValue store key value = liftIO $ atomicModifyIORef' store modify
where modify kv = (insert key value kv, key)
Almost there. We declare the API we want to serve:
kvAPI :: Proxy API
kvAPI = Proxy
Once again, Kwang Yul Seo has a great blog post on this.
Finally, we define our entry point:
main :: IO ()
main = do
port <- read . head <$> getArgs :: IO Int
run port . serve kvAPI . server =<< newIORef empty
To recap: we define our API as a type, our handlers, and the type we want to serve, and then we plug it all together.
And we're done! We can now chmod +x KVStore.hs
(or whatever you called the
file) and run it: ./KVStore.hs 8081
.
I hope this provides a better starting point for learning Servant. If desired, the full script is available here.
Comments !


Class: WsdlMapper::Dom::Bounds
- Inherits:
-
Object
- Object
- WsdlMapper::Dom::Bounds
- Defined in:
- lib/wsdl_mapper/dom/bounds.rb
Instance Attribute Summary (collapse)
-
- (Object) max
Returns the value of attribute max.
-
- (Object) min
Returns the value of attribute min.
-
- (Object) nillable
Returns the value of attribute nillable.
Instance Method Summary (collapse)
- - (Object) dup
-
- (Bounds) initialize(min: nil, max: nil, nillable: nil)
constructor
A new instance of Bounds.
Constructor Details
- (Bounds) initialize(min: nil, max: nil, nillable: nil)
Returns a new instance of Bounds
6 7 8 |
# File 'lib/wsdl_mapper/dom/bounds.rb', line 6 def initialize(min: nil, max: nil, nillable: nil) @min, @max, @nillable = min, max, nillable end |
Instance Attribute Details
- (Object) max
Returns the value of attribute max
4 5 6 |
# File 'lib/wsdl_mapper/dom/bounds.rb', line 4 def max @max end |
- (Object) min
Returns the value of attribute min
4 5 6 |
# File 'lib/wsdl_mapper/dom/bounds.rb', line 4 def min @min end |
- (Object) nillable
Returns the value of attribute nillable
4 5 6 |
# File 'lib/wsdl_mapper/dom/bounds.rb', line 4 def nillable @nillable end |
Instance Method Details
- (Object) dup
10 11 12 |
# File 'lib/wsdl_mapper/dom/bounds.rb', line 10 def dup Bounds.new min: @min, max: @max, nillable: @nillable end |


Week 3
Monday
Snap Challenge - Calculator
Identify CapStone Projects
Agile Project Management
Homework - ProDevDoc Daily update
Homework - CapStone - Prepare
Homework - PWP Due Thursday Morning
Tuesday
Snap Challenge - CSS
Advanced Object Oriented
Gym
Professional Development - Team Building
Ted Talk - Skipped for tutoring
Tutoring - OOP Code Review
Homework - ProDevDoc Daily update
Homework - CapStone - datadesign
Homework - PWP Due Thursday Morning
Wednesday
Snap Challenge - Composition
Unit Testing
Homework - ProDevDoc Daily update
Homework - CapStone - datadesign
Homework - PWP Due Thursday Morning
Thursday
Snap Challenge - CSS
Bootstrap
Review Entities with Bridge
Gym
Homework - ProDevDoc Daily update
Homework - CapStone - ERD
Friday
Snap Challenge - Jeopardy
Day of Code
Two exceptional speakers
Review Entities with Bridge
Homework - Bootstrap Challenge
Homework - ProDevDoc Daily update
Homework - CapStone - ERD
Homework - prodev weekly update
Week 3 started well for me. I spent alot of time studying CSS and OOP and they are both starting to make sense to me. I am working on templates for CSS and OOP in preperation for the CapStone project. Our team had a challenge getting the ERD approved this weekend as there was no bridge support on Slack from Saturday noon thru Monday morning. I am disappointed about the lack of feedback from the bridge this weekend. Their complete lack of communication over the weekend was unprofessional. In the future, if the bridge can not meet their committment to support the class on Slack, the bridge needs to notify the class that they are going off-line so the team can adjust their expectations and alter their schedule.


SabrapaySpringApplication |
class SabrapaySpringApplication |
SabrapayWebClient |
class SabrapayWebClient |
SecurityWebInitializer |
class SecurityWebInitializer : WebSecurityConfigurerAdapter |
main |
fun main(args: Array<String>): Unit |


test-summary
hhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyappy
hhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyhappyappy


Computations in local rings can be handled via standard bases with respect to local monomial orders.
* Local monomial orders and standard bases
* Bases of standard monomials and multiplicity
* Applications: Milnor number
For the ideal
R = QQ[x,y];
I = ideal(y^4*x+3*x^3-y^4-3*x^2,
x^2*y-2*x^2,
2*y^4*x-x^3-2*y^4+x^2);
we can see that V(I) = {(0,0), (1,2) } : the radical of I decomposes in
decompose I
Suppose we would like to do local computations at the origin. To that define a ring
R0 =QQ[x,y,Weights=>{-1,-1},Global=>false]
Note that R0 is not a local ring per se, but the specified monomial order is a refinement of the weight order with negative weights and is, therefore, local.
getGB = flatten@@entries@@gens@@gb
J = sub(I,R0)
G = getGB sub(J,R0)
The set G above is a standard basis in the local ring RM for M=<x,y>. Note that it can be simplified: 2x2-x2y = (2-y)x2, but (2-y) is a unit in RM. Therefore, {x2,y4-(4/3)xy4} is a standard basis as well and, by a similar argument,
G0 = {x^2, y^4}
is one as well.
The basis of standard monomials (do not confuse with standard basis) for the quotient ring in our running example can be obtained by taking a quotient with respect to ideal generated by any version of a standard basis above:
basis(R0/G)
basis(R0/G0)
The multiplicity of the origin equals the vector space dimension of the quotient local ring or, in another terminology, its degree.
degree(R0/G)
The multiplicity of the other point in V(I) can be computed by shifting (1,2) to the origin.
J' = sub(sub(J,{x=>x+1,y=>y+2}),R0)
getGB J'
From the leading monomials of the standard basis elements above, we see that the other point is regular (multiplicity=1).
degree J'
As we know the sum of multiplicities equals the multiplicity (vector-space dimension) of R/I.
degree J' + degree J == degree I
Take a polynomial function
use R0
h = poly "x5+y2+2y3+y4"
The partial derivatives of h
J = jacobian matrix{{h}}
vanish at the origin. That means that h has a singularity at (0,0), moreover, this singularity is isolated: the localization ideal generated by the derivatives is 0-dimensional.
I = ideal J
dim I
A singularity theory invariant, the Milnor number, is defined as the multiplicity of the singular point and, therefore, can be computed using standard bases technique.
degree I
Note that there are other singularities: (0,-1) and (0,-1/2)). Their Milnor numbers are the same:
degree sub(I,{x=>x,y=>y-1})
degree sub(I,{x=>x,y=>y-1/2})
The geometric meaning of this invariant may be demonstrated by perturbing the function:
QQ[x,y,a,b]
h' = poly "x5+y2+2y3+y4+ax+by"
I = ideal {diff(x,h'),diff(y,h')}
We can see that there are, for example, 4 isolated regular points of V(I) close to (0,0) if a and b are small.


Bio
Hello! My name is Clarice. I am 23 years old and live in Bellevue. I love videogames and reading!
Experience
- I have created a Website with the help of teammates in Coding 101
- Completed Coding 101
- Currently enrolled in Coding 201
- More expereince coming soon!
Education
- CodeFellows | Software Developement | Ongoing
- Shoreline Community College | Accounting | 2017 - 2018
- Gary Manuel Aveda Institute | Cosmetology |2015 - 2016
Goals
- Complete CodeFellows in a timely manner
- Get a web developement job in the Greater Seattle area
- Learn how to learn code better
- Grow my value as an employee
Top Ten Favorite Foods
- Pad Kee Mao
- Sushi
- Tempura Udon
- Jambalia
- Pho
- Mac and Cheese
- Cheese Fondue
- Nachos
- Potato Chips
- Wings and Beer


Title and author:
Pêndulo Simples
Humberto Carmona
© 2020, Humberto Carmona, H. Carmona. Compiled with EJS 6.01 (201013)
Released under a
license.


O BROTHER, WHERE ART THOU? A film review by Mark R. Leeper
Capsule: In 1937 Mississippi three fugitives from a chain gang race to save a treasure from being flooded by a new dam. Lacking the power of the best of the Coen Brothers, this is a sly little Southern Odyssey with more than its share of chuckles. The story works only in episodes but the unusual time and setting and the odd characterization pull the film along. Rating: 7 (0 to 10), low +2 (-4 to +4)
The 1941 Preston Sturges comedy SULLIVAN'S TRAVELS told of a movie director who decided that in the hard depression era, fluffy comedies just were not what the world needed. He wants to make a serious film about the down-trodden in the South. When the director sees the real world he discovers what the world really needs is more fluffy comedies like . . . well, like that Preston Sturges guy makes. On to the ash heap go his plans to make the serious and important film O BROTHER, WHERE ART THOU? Apparently Joel and Ethan Coen have decided to make a film called O BROTHER, WHERE ART THOU? after all. In spite of the dour title it seems that neither Sullivan nor the Coens could resist the urge to make fluff that belies the harsh setting.
The plot is simple enough. We start with a chain gang working soulfully in the blistering Mississippi sun. Somehow three men have managed to escape (as convicts always seem to from cinematic chain gangs) and are hiding in a cornfield. They are hobbled by a chain around their ankles and betrayed by their telltale broad-striped prison clothing. There is Everett Ulysses McGill (played by George Clooney), Pete Hogwollop (John Turturro), and Delmar O'Donnel (Tim Blake Nelson. The story follows them on a short odyssey into the poor South past sights and though a series of episodes, some of which will be drawn together in the final reel. Along the way they pick up and then lose a black guitarist Tommy Johnson (Chris Thomas King). They mix into music, and politics; they see a famous criminal's getaway, a baptism, and a Klan rally. In the end they have multiple whimsical Dei Ex Machinae. Some of the incidents are loosely and slightly pretentiously based on episodes of Homer's Odyssey. Others are inspired by SULLIVAN'S TRAVELS and perhaps bits of other films set in the Depression-era South like NIGHT OF THE HUNTER and FOOLS' PARADE and Davis Grubb stories.
The Coen Brothers are, of course, some of the most creative filmmakers going. This film is released by the unconventional combination of Touchstone and Universal. Roger Deakins shot the entire film with washed out colors to give the film something of a period feel. It works, though I am not sure why. They drop into scenes 1930s products, particularly hair pomade for the dapper Ulysses. They avoided two pitfalls here. They used no brands currently available so they respected their film sufficiently to avoid product placements. They also avoided that great cliche of the South, Moon Pies. One cliche they did not avoid is the choreographed and slightly too poetic chain gang. It always seems like an appeal to social conscience to show men chained up, though how different is it from children led together through town on a rope as we see in the latter portion of the film.
The music by T-Bone Burnett and others becomes an important element of the film rather than just creating atmosphere for incidents. The movie is suffused with the "Old Timey" music of the period which becomes important in the plot. There is a repeating theme of the characters getting into strange circumstances by following mystical music coming from the woods. Each time it is heard the boys will be tested in some way. Tim Blake Nelson is not one of the more familiar faces on the screen but manages to stand up with the more popular Clooney and Turturro, though in the musical scenes he seems relegated to a distinct third place. Also along in much smaller roles are familiar Coen veterans Charles Durning, John Goodman, and Holly Hunter.
As a single story, O BROTHER, WHERE ART THOU? just does not amount to much. But the individual episodes are entertaining. I rate it a 7 on the 0 to 10 scale and a low +2 on the -4 to +4 scale.
Mark R. Leeper<EMAIL_ADDRESS>Copyright 2001 Mark R. Leeper
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


Welcome to Direct Grants!
In this day and age, we understand the importance of saving time. Typical home grant searches, however, are notorious for consuming large amounts of time with little to no gain. Here at Direct Grants we're dedicated to finding you the exact home grant you need. No run-around, no endless phone calls, no time wasted; just direct access to the specific grant you need. On this site you'll find different organizations in your area, the size of the grant these organizations typically give, and what their grant requirements are. All of these organizations can be searched through and filtered based upon your criteria so you can find the grant you need to get the perfect home. To get to our organizations page, click here, or simply click the link at the top of the page.


I choose Python Django Framework.
First of all, I have to install Django without sudo
. I find pip install --user
extremely useful and succeed after a lot of effort.
Second, in order to deploy Django in Apache, I manually build mod_wsgi.so and load this module in httpd.conf. Thus when Apache detects /django
in URL, Django will be called to handle it. Since the built-in web server in Django is too weak to be deployed in production environment, I am not able to use Reverse Proxy or Load Balance.
At last, the most important part is coding. Django is a MVC framework, but the names are different:
Here is what every file does:
Guest
with four attributes (name, age, gender, email)
and interact with database automatically./django/polls/query/
comes, the function query
in views.py is called.query
fetches all Guest
entries from the database and send them to templates/query.html. <xsl:attribute>
to insert images.<xsl:choose>
and <xsl:when>
to insert horizontal lines between recipes.Research by loading these files on as many different browser types and versions as you can find (e.g. IE, Opera, Safari and Mozilla family). Snapshot what happens and include these images on your web site.
Experiment with applet graphics (AWT and Swing). Create a simple applet to draw a red square and a green circle.
Add to your applet to draw a third shape, determined by parameters passed from the HTML file that determine the shape and colour to draw.
Write a brief essay outlining the differences between AWT and Swing graphics.
For additional credit: use threads to implement some sort of animation ... try moving the shapes around.
JavaScript browser sniffers are techniques written in Javascript in order to determine the web browser a visitor is using, and to serve browser-appropriate content to the visitor.
Because Javascript relies heavily on DOM, whose interpretation varies in different browsers. To circumvent incompatibilities between browsers and ensure consistent display of content, browser sniffers are used.
Here is what you must achieve:
Sample.java
program so that the label (jLabel1
)
shows your group number (e.g. 99
).RunSample.java
program so that it runs with your
group identity (e.g. y2006g99
).BufferedReader
with a
Scanner
and link your code on your web pageGive the SELECT command to answer the question : Who will go to the party held in Peking University?
SELECT Guest_Name
FROM guest, party, guest_party
WHERE party.Party_Num = guest_party.Party_Num
AND guest_party.Guest_ID = guest.Guest_ID
AND Place='Peking University'
Figure out a way to obtain the column labels returned by mysql_fetch_array.
$row = mysql_fetch_array($result);
while ($element = each($row)) {
$element = each($row);
print $element['key'];
}
Tell the difference between a CGI program and a PHP program executed by a Web Server.
Set up PHP for your Apache and test it.
See PHPInfoTransform the guest book that you implemented in week 3 into a Database version.
Click to show answerFor homework3, only one Table is created. This time, create the other two tables, party table and party_guest table( you can choose different table names). Functions realized include:
1) Guest can find out what parties will be given and when and where.
2) In additon to Guest name, Age, Gender, .... User can choose what parties he or she wants to go.
3) Separate PHP programs are provided for the organizer to input information about the parties will be given.
See the link in Homework 3Realize more query functions.
4) Guests can find out what parties will every guest attend.
5) Organizers can find out who will come to their parties and delete useless party items.
Implement a page layout like:
Use CSS and DIV.Your performance of designing the layout will be evaluated in Firefox 3.5. However, you are encouraged to test your layout in as many browsers as possible. You will receive a bonus if you design layout that has the same appearance in IE6, IE7+, Opera, Safari, etc. and mention this effort in your homework. Remember Maxthon, Sogou and Tencent Traveler are not considered as individual browsers, for they are only shell of IE. Also, try to pass both the W3C Markup Validation and the W3C CSS Validation, then place "valid" icon(s) on your web pages. The icons can be found on the page showing you passed validation.
- The left column and the right column must be of the same width, which could be fixed.
- The right column always floats to the right.
- The left column and the right column must be of the same height no matter what do they contain.
- Inline style, Internal style sheet and External style sheet must all be used in the homework.
Study the "Survey" example in the text book carefully. Try it first.
Implement a little "Questionare" using Perl. Information to be collected include:
- Name.
- Age.
- Gender.
- E_mail address.
In addition to the Submit button, a Query button is provided for the user to see all data items have been collected. User should also be able to delete selected Items.
Tips:
- Three CGI program are needed, one to collect data items, one to list items, the other to delete selected items.
- To delete items, for convenience, a check box could be provide to each data item being listed. After user click "delete", all checked items are deleted.
- Error check for the input data is not required.
- To prevent simultaneous accesses to a single file, "flock" function is used. please refer to the Survey example in the text book.
- In order to be run, the CGI program file should has a file mode of 711, which says it can be executed by any user.
- Put the CGI program in the directory of "cgi-bin" under you Server root Directory.
- The file which saves the collected data should be created first and should be writable for other users. A file mode 666 is needed.
- The error.log file in the directory of "logs" will help you to debug your CGI program in case of error.
You are encouraged to improve this questionaire in some way. For example, you could use AJAX to update the information without refreshing the page, write three versions of this questionaire in Perl, C CGI and Shell CGI, or make pages produced by CGI pass W3C validations. Any efforts made should be listed in your pages as they deserve a reward.
Create a HTML pages including :
HTML validation PASSED
For the three categories of web software technology we've just defined, you are asked to find out which category so-called "Netscape plug-in" technology belongs to. Please describe the basic idea of the technology, and give your reasoning on its position in the technology categories.
In a normal intercommunication between Web browser and Web Server, Web Server closes the network communication right after it sends back the document requested by the browser. Think about this statusless character of World Wide Web. Give some good points and bad points of it. Can a web server know exactly how many browsers are reading its document?
Use some Web search engine to search keywords regarding your interest (even your own name to find out how famous you are in this Web World). Recommended search engines are http://www.baidu.com/ and http://www.google.com/