Modern RPC

With modern RPC, we refer to RPC through RESTful resources. A Bounded Context reveals a clear interface to interact with to the outside world. It exposes resources that could be manipulated through HTTP verbs. We could say that the Bounded Context offers a set of services and operations. In strategical terms, this is what is called an Open Host Service.

Open Host Service
Define a protocol that gives access to your subsystem as a set of SERVICES. Open the protocol so that all who need to integrate with you can use it. Enhance and expand the protocol to handle new integration requirements, except when a single team has idiosyncratic needs. Then, use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent. Eric Evans - Domain-Driven Design: Tackling Complexity in the Heart of Software.

Let's explore an example provided within the Last Wishes application that comes with this book's GitHub organization.

The application is a web platform with the purpose of letting people save their last wills before they die. There are two Contexts: one responsible for handling wills—the Will Bounded Context—and one in charge of giving points to the users of the system—the Gamification Context. In the Will Context, the user could have badges related to the number of points the user made on the Gamification Context. This means that we need to integrate both Contexts together in order to show the badges a user has on the Will Context.

The Gamification Context is a full-fledged event-driven application powered by a custom event sourcing engine. It's a full-stack Symfony application that uses FOSRestBundle, BazingaHateoasBundle, JMSSerializerBundle, NelmioApiDocBundle, and OngrElasticsearchBundle to provide a level 3 and up REST API (commonly known as the Glory of REST), according to the Richardson Maturity Model. All the Events triggered within this Context are projected against an Elasticsearch server, in order to produce the data needed for the views. We'll expose the number of points made for a given user through an endpoint like http://gamification.context.host/api/users/{id}.

We'll also fetch the user projection from Elasticsearch and serialize it to a format previously negotiated with the client:

namespace AppBundleController;

use FOSRestBundleControllerAnnotations as Rest;
use FOSRestBundleControllerFOSRestController;
use NelmioApiDocBundleAnnotationApiDoc;

class UsersController extends FOSRestController
{
/**
* @ApiDoc(
* resource = true,
* description = "Finds a user given a user ID",
* statusCodes = {
* 200 = "Returned when the user have been found",
* 404 = "Returned when the user could not be found"
* }
* )
*
* @RestView(
* statusCode = 200
* )
*/
public function getUserAction($id)
{
$repo = $this->get('es.manager.default.user');
$user = $repo->find($id);

if (!$user) {
throw $this->createNotFoundException(
sprintf(
'A user with an ID of %s does not exist',
$id
)
);
}
return $user;
}
}

As we explained in the Chapter 2, Architectural Styles reads are treated as an Infrastructure concern, so there's no need to wrap them inside a Command / Command Handler flow.

The resulting JSON+HAL representation of a user will be like this:

{
"id": "c3c587c6-610a-42df",
"points": 0,
"_links": {
"self": {
"href":
"http://gamification.ctx/api/users/c3c587c6-610a-42df"
}
}
}

Now we're in a good position to integrate both Contexts. We just need to write the client in the Will Context for consuming the endpoint we've just created. Should we mix both Domain Models? Digesting the Gamification Context directly will mean adapting the Will Context to the Gamification one, resulting in a Conformist integration. However, separating these concerns seems worth the effort. We need a layer for guaranteeing the integrity and the consistency of the Domain Model within the Will Context, and we need to translate points (Gamification) to badges (Will). In Domain-Driven Design, this translation mechanism is what's called an Anti-Corruption layer.

Anti-Corruption Layer
Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models. Eric Evans - Domain-Driven Design: Tackling Complexity in the Heart of Software.

So, what does the Anti-Corruption layer look like? Most of the time, Services will be interacting with a combination of Adapters and Facades. The Services encapsulate and hide the low-level complexities behind these transformations. Facades aid in hiding and encapsulating access details required for fetching data from the Gamification model. Adapters translate between models, often using specialized Translators.

Let's see how to define a User Service within the Will's model that will be responsible for retrieving the badges earned by a given user:

namespace LwDomainModelUser;

interface UserService
{
public function badgesFrom(UserId $id);
}

Now let's look at the implementation on the Infrastructure side. We'll use an adapter for the transformation process:

namespace LwInfrastructureService;

use LwDomainModelUserUserId;
use LwDomainModelUserUserService;

class TranslatingUserService implements UserService
{
private $userAdapter;

public function __construct(UserAdapter $userAdapter)
{
$this->userAdapter = $userAdapter;
}

public function badgesFrom(UserId $id)
{
return $this->userAdapter->toBadges($id);
}
}

And here's the HTTP implementation for the UserAdapter:

namespace LwInfrastructureService;

use GuzzleHttpClient;

class HttpUserAdapter implements UserAdapter
{
private $client;

public function __construct(Client $client)
{
$this->client = $client;
}

public function toBadges( $id)
{
$response = $this->client->get(
sprintf('/users/%s', $id),
[
'allow_redirects' => true,
'headers' => [
'Accept' => 'application/hal+json'
]
]
);

$badges = [];
if (200 === $response->getStatusCode()) {
$badges =
(new UserTranslator())
->toBadgesFromRepresentation(
json_decode(
$response->getBody(),
true
)
);
}
return $badges;
}
}

As you can see, the Adapter acts as a Facade to the Gamification Context too. We did it this way, as fetching the User resource on the Gamification side is pretty straightforward. The Adapter uses the UserTranslator to perform the translation:

namespace LwInfrastructureService;

use LwInfrastructureDomainModelUserFirstWillMadeBadge;
use SymfonyComponentPropertyAccessPropertyAccess;

class UserTranslator
{
public function toBadgesFromRepresentation($representation)
{
$accessor = PropertyAccess::createPropertyAccessor();
$points = $accessor->getValue($representation, 'points');
$badges = [];
if ($points > 3) {
$badges[] = new FirstWillMadeBadge();
}
return $badges;
}
}

The Translator specializes in transforming the points coming from the Gamification Context into badges.

We've shown how to integrate two Bounded Contexts where respective teams share a Customer-Supplier relationship. The Gamification Context exposes the integration through an Open Host Service implemented by a RESTful protocol. On the other side, the Will Context consumes the service through an Anti-Corruption layer responsible for translating the model from one Domain to the other, ensuring the Will Context's integrity.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.249.104