[skip appveyor] Moar documentation

This commit is contained in:
Mark van Renswoude 2019-08-15 17:11:54 +02:00
parent 196aa63a4a
commit a74924af90
2 changed files with 87 additions and 20 deletions

View File

@ -14,7 +14,7 @@ See :doc:`indepth` on defining request - response messages.
Enabling Tapeti Flow
--------------------
To enable the use of Tapeti Flow, install the Tapeti.Flow NuGet package and call WithFlow() when setting up your TapetiConfig:
To enable the use of Tapeti Flow, install the Tapeti.Flow NuGet package and call ``WithFlow()`` when setting up your TapetiConfig:
::
@ -25,9 +25,9 @@ To enable the use of Tapeti Flow, install the Tapeti.Flow NuGet package and call
Starting a flow
---------------
To start a new flow you need to obtain an IFlowStarter from your IoC container. It has one method in various overloads: Start.
To start a new flow you need to obtain an IFlowStarter from your IoC container. It has one method in various overloads: ``Start``.
Flow requires all methods participating in the flow, including the starting method, to be in the same controller. This allows the state to be stored and restored when the flow continues. The IFlowStarter.Start call does not need to be in the controller class.
Flow requires all methods participating in the flow, including the starting method, to be in the same controller. This allows the state to be stored and restored when the flow continues. The ``IFlowStarter.Start`` call does not need to be in the controller class.
The controller type is passed as a generic parameter. The first parameter to the Start method is a method selector. This defines which method in the controller is called as soon as the flow is initialised.
@ -35,7 +35,7 @@ The controller type is passed as a generic parameter. The first parameter to the
await flowStart.Start<QueryBunniesController>(c => c.StartFlow);
The start method can have any name, but must be annotated with the [Start] attribute. This ensures it is not recognized as a message handler. The start method and any further continuation methods must return either Task<IYieldPoint> (for asynchronous methods) or simply IYieldPoint (for synchrnous methods).
The start method can have any name, but must be annotated with the ``[Start]`` attribute. This ensures it is not recognized as a message handler. The start method and any further continuation methods must return either Task<IYieldPoint> (for asynchronous methods) or simply IYieldPoint (for synchronous methods).
::
@ -80,11 +80,11 @@ Continuing a flow
-----------------
When starting a flow you're most likely want to start with a request message. Similarly, when continuing a flow you have the option to follow it up with another request and prolong the flow. This behaviour is controlled by the IYieldPoint that must be returned from the start and continuation handlers. To get an IYieldPoint you need to inject the IFlowProvider into your controller.
IFlowProvider has a method YieldWithRequest which sends the provided request message and restores the controller when the response arrives, calling the response handler method you pass along to it.
IFlowProvider has a method ``YieldWithRequest`` which sends the provided request message and restores the controller when the response arrives, calling the response handler method you pass along to it.
The response handler must be marked with the [Continuation] attribute. This ensures it is never called for broadcast messages, only when the response for our specific request arrives. It must also return an IYieldPoint or Task<IYieldPoint> itself.
The response handler must be marked with the ``[Continuation]`` attribute. This ensures it is never called for broadcast messages, only when the response for our specific request arrives. It must also return an IYieldPoint or Task<IYieldPoint> itself.
If the response handler is not asynchronous, use YieldWithRequestSync instead, as used in the example below:
If the response handler is not asynchronous, use ``YieldWithRequestSync`` instead, as used in the example below:
::
@ -124,7 +124,7 @@ If the response handler is not asynchronous, use YieldWithRequestSync instead, a
}
}
You can once again return a YieldWithRequest, or end it.
You can once again return a ``YieldWithRequest``, or end it.
Ending a flow
-------------
@ -188,26 +188,79 @@ Instead of manually starting a flow, you can also start one in response to an in
});
}
.. ::note If the message that started the flow was a request message, you must end the flow with EndWithResponse or you will get an exception. Likewise, if the message was not a request message, you must end the flow with End.
.. note:: If the message that started the flow was a request message, you must end the flow with EndWithResponse or you will get an exception. Likewise, if the message was not a request message, you must end the flow with End.
Parallel requests
-----------------
When you want to send out more than one request, you could chain them in the response handler for each message. An easier way is to use ``YieldWithParallelRequest``. It returns a parallel request builder to which you can add one or more requests to be sent out, each with it's own response handler. In the end, the Yield method of the builder can be used to create a YieldPoint. It also specifies the converge method which is called when all responses have been handled.
.. error:: You've stumbled upon a piece of unfinished documentation.
Behind you is all prior knowledge. In front of you is nothing but emptyness. What do you do?
An example:
1. Attempt to explore further
2. Complain to the author and demand your money back
3. Abandon all hope
::
> |
public IYieldPoint HandleBirthdayMessage(RabbitBirthdayMessage message)
{
var sendCardRequest = new SendCardRequestMessage
{
RabbitID = message.RabbitID,
Age = message.Age,
Style = CardStyles.Funny
};
var doctorAppointmentMessage = new DoctorAppointmentRequestMessage
{
RabbitID = message.RabbitID,
Reason = "Yearly checkup"
};
return flowProvider.YieldWithParallelRequest()
.AddRequestSync<SendCardRequestMessage, SendCardResponseMessage>(
sendCardRequest, HandleCardResponse)
.AddRequestSync<DoctorAppointmentRequestMessage, DoctorAppointmentResponseMessage>(
doctorAppointmentMessage, HandleDoctorAppointmentResponse)
.YieldSync(ContinueAfterResponses);
}
[Continuation]
public void HandleCardResponse(SendCardResponseMessage message)
{
// Handle card response. For example, store the result in a public field
}
[Continuation]
public void HandleDoctorAppointmentResponse(DoctorAppointmentResponseMessage message)
{
// Handle appointment response. Note that the order of the responses is not guaranteed,
// but the handlers will never run at the same time, so it is safe to access
// and manipulate the public fields of the controller.
}
private IYieldPoint ContinueAfterResponses()
{
// Perform further operations on the results stored in the public fields
// This flow did not start with a request message, so end it normally
return flowProvider.End();
}
A few things to note:
#) The response handlers do not return an IYieldPoint themselves, but void (for AddRequestSync) or Task (for AddRequest). Therefore they can not influence the flow. Instead the converge method as passed to Yield or YieldSync determines how the flow continues. It is called immediately after the last response handler.
#) The converge method must be private, as it is not a valid message handler in itself.
Note that you do not have to perform all the operations in one go. You can store the result of ``YieldWithParallelRequest`` and conditionally call ``AddRequest`` or ``AddRequestSync`` as many times as required.
.. warning:: At the time of writing, you must add at least one request to the parallel request builder before yielding or your flow will halt. This will hopefully be fixed in the future.
Persistent state
----------------
By default flow state is only preserved while the service is running. To persist the flow state across restarts and reboots, provide an implementation of IFlowRepository to WithFlow().
By default flow state is only preserved while the service is running. To persist the flow state across restarts and reboots, provide an implementation of IFlowRepository to ``WithFlow()``.
::
@ -229,7 +282,7 @@ Tapeti.Flow includes an implementation for SQL server you can use as well. First
constraint PK_Flow primary key clustered(FlowID)
);
Then install the Tapeti.Flow.SQL NuGet package and register the SqlConnectionFlowRepository by passing it to WithFlow, or by using the WithFlowSqlRepository extension method before calling WithFlow:
Then install the Tapeti.Flow.SQL NuGet package and register the SqlConnectionFlowRepository by passing it to WithFlow, or by using the ``WithFlowSqlRepository`` extension method before calling ``WithFlow``:
::

View File

@ -8,6 +8,22 @@ As described in the Getting started guide, a message is a plain object which can
When communicating between services it is considered best practice to define messages in separate class library assemblies which can be referenced in other services. This establishes a public interface between services and components without binding to the implementation.
Enums
-----
Special care must be taken when using enums in messages. For example, you have several services consuming a message containing an enum field. Some services will have logic which depends on a specific value, others will not use that specific field at all.
Then later on, you want to add a new value to the enum. Some services will have to be updated, but the two examples mentioned above do not rely on the new value. As your application grows, it will become unmanageable to keep all services up-to-date.
Tapeti accounts for this scenario by using a custom deserializer for enums. Enums are always serialized to their string representation, and you should never rely on their ordinal values. When deserializing, if the sending service sends out an enum value that is unknown to the consuming service, Tapeti will deserialize it to an out-of-bounds enum value instead of failing immediately.
This effectively means that as long as your code has conditional or switch statements that can handle unknown values, or only perform direct comparisons, the existing logic will run without issues.
In addition, Tapeti does not allow you to pass the value as-is to another message, as the original string representation is lost. If it detects the out-of-bounds value in an enum field of a message being published, it will raise an exception.
However, Tapeti cannot analyze your code and the ways you use the enum field. So if you ignored all advice and used the ordinal value of the enum to store somewhere directly, you are left with the value 0xDEADBEEF, and you will now know why.
.. _declaredurablequeues:
Durable queues
@ -105,8 +121,6 @@ Messages like these must not be lost, there should always be a queue bound to it
}
Routing keys
------------
The routing key is determined by converting CamelCase to dot-separated lowercase, leaving out "Message" at the end if it is present. In the example below, the routing key will be "something.happened":