docs: update reactive messaging

(cherry picked from commit 1119f2971a)

# Conflicts:
#	doc/Learn/Mvux/Advanced/Messaging.md
This commit is contained in:
Andres Pineda 2024-08-30 19:44:50 -04:00 коммит произвёл Mergify
Родитель 8f64480751
Коммит 434de56ed8
1 изменённых файлов: 43 добавлений и 19 удалений

Просмотреть файл

@ -4,25 +4,39 @@ uid: Uno.Extensions.Mvux.Advanced.Messaging
# Messaging
Messaging is the ability to send in-app messages between its components in a way that enables them to remain decoupled from one another.
Messaging is the ability to send in-app messages between its components to enable them to remain decoupled from one another.
<<<<<<< HEAD
In MVUX, we use feeds to pull entities from a service. When a command or an action is executed, we call methods on the service that apply changes to the data, for example, an entity creation, removal, or update.
But when the data is changed by the service, we need to ping back the feed and tell it that a change has taken place and that it should update the changed entities, but at the same time, we don't want the service to have a reference to the model or know about it; it's the model that uses the service, not the other way around.
=======
In MVUX, we use `Feeds` to pull entities from a service. When executing a command or action, we call methods on the service that apply changes to the data, such as entity creation, removal, or update.
However, when the service changes the data, it's not a one-way street. We need to notify the feed that a change has occurred and that it should update the affected entities. But we also want to maintain the decoupling. The service shouldn't have a reference to the model or know about it; it's the model that uses the service, not the other way around.
>>>>>>> 1119f2971 (docs: update reactive messaging)
This is where messaging comes in handy. The service sends a message about this entity change to a central messenger that publishes messages to anyone willing to listen. The model then subscribes to the messages it wants to listen to (filtered by type of entity and entity key) and updates its feeds with the updated entities received from the service.
Here is where messaging comes in handy. The service sends a message about this entity change to a central messenger that publishes messages to anyone willing to listen. The model then subscribes to the messages it wants to listen to (filtered by type of entity and entity key) and updates its feeds with the updated entities received from the service.
## Community Toolkit messenger
The Community Toolkit messenger is a common tool that can use to send and receive such messages between objects in the app. The messenger enables objects to remain decoupled from each other without keeping a strong reference between the sender and the receiver. The messages can also be sent over specific channels uniquely identified by a token or within certain areas of the application.
The Community Toolkit messenger is a standard tool for sending and receiving messages between app objects. It enables objects to remain decoupled from each other without keeping a strong reference between the sender and the receiver. Messages can also be sent over specific channels uniquely identified by a token or within certain application areas.
<<<<<<< HEAD
The core component of the messenger is the `IMessenger` object. Its main methods are `Register` and `Send`. `Register` subscribes to an object to start listening to messages of a certain type, whereas `Send` sends out messages to all listening parties.
There are various ways to obtain the `IMessenger` object, but we'll use the most common one, which involves using [Dependency Injection](xref:Uno.Extensions.DependencyInjection.Overview) (DI) to register the `IMessenger` service in the app so it can then be resolved at the construction of other dependent types (e.g., ViewModels).
In the model, we obtain a reference to the `IMessenger` on the constructor, which is resolved by the DI's service provider.
The `Register` method has quite a few overloads, but for the sake of this example, let's use the one that takes the recipient and the message type as parameters. The first parameter is the recipient (`this` in this case), and the second one is a callback that is executed when a message has been received. Although `this` can be called within the callback, it's preferred that the callback doesn't make external references and that the `MyModel` is passed in as an argument.
`MessageReceived` is then called on the received recipient (which is the current `MyModel`), and the message is passed into it:
=======
The core component of the messenger is the `IMessenger` object. Its primary methods are `Register` and `Send.` `Register` subscribes to an object to start listening to messages of a specific type, whereas `Send` sends messages to all listening parties.
There are various ways to obtain the `IMessenger` object. Still, we'll use the most common one, which involves using [Dependency Injection](xref:Uno.Extensions.DependencyInjection.Overview) (DI) to register the `IMessenger` service in the app so it can then be resolved when other dependent types (e.g., ViewModels) are constructed.
MVUX includes extension methods that enable the integration between the [Community Toolkit messenger](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/messenger) and [MVUX feeds](xref:Uno.Extensions.Mvux.Feeds). But before discussing how MVUX integrates with the Community Toolkit messenger, let's have a quick look at how the messenger works.
In the model, we obtain a reference to the `IMessenger` on the constructor, which the DI's service provider resolves.
The `Register` method has quite a few overloads, but for the sake of this example, let's use the one that takes the recipient and the message type as parameters. The first parameter is the recipient (`this` in this case), and the second is a callback executed when a message has been received. Although `this` can be called within the callback, it's preferred that the callback doesn't make external references and that the `MyModel` is passed in as an argument.
`MessageReceived` is then called on the recipient (the current `MyModel`), passing the message to it.
>>>>>>> 1119f2971 (docs: update reactive messaging)
MVUX includes extension methods that enable the integration between the [Community Toolkit messenger](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/messenger) and [MVUX feeds](xref:Uno.Extensions.Mvux.Feeds). But before discussing how MVUX integrates with the Community Toolkit messenger, let's quickly look at how the messenger works.
```csharp
using CommunityToolkit.Mvvm.Messaging;
@ -82,21 +96,25 @@ public partial record AnotherModelOrService
MVUX harnesses the power of the Community Toolkit messenger and adds extension methods that enable you to listen to entity changes received from the messenger and have them automatically applied to the state or list-state storing the entities in the current model. The following entity-change types are supported: created, updated, and deleted.
For instance, when there is a command in the model that, when executed, creates a new entity and stores it in the database using a service, the service can send a 'created' entity-change message with the messenger, which can then be intercepted in the model to have the state or list-state update itself and display the newly created entity received from the messenger.
For instance, when a command in the model creates a new entity and stores it in the database using a service, the service can send a 'created' entity-change message to the messenger, which can then be intercepted in the model to have the `State` or `ListState` update itself and display the newly created entity received from the messenger.
These extensions are shipped in the [`Uno.Extensions.Reactive.Messaging`](https://www.nuget.org/packages/Uno.Extensions.Reactive.Messaging) NuGet package.
These extensions are part of the [`Uno.Extensions.Reactive.Messaging`](https://www.nuget.org/packages/Uno.Extensions.Reactive.Messaging) NuGet package.
### Observe
The purpose of the `Observe` methods (it comes in several overloads, [see below](#additional-observe-overloads)), is to intercept entity-change messages (`EntityMessage<T>`) from the Community Toolkit messenger and apply them to the designated state or list-state.
The purpose of the `Observe` methods (it comes in several overloads, [see below](#additional-observe-overloads)) is to intercept entity-change messages (`EntityMessage<T>`) from the Community Toolkit messenger and apply them to the designated state or list-state.
In the example below, there is a model that displays a state-list of `Person` entities received from a service, loaded using a state, with the [`Async`](xref:Uno.Extensions.Mvux.ListStates#async) factory method.
In the example below, a model displays a `StateList` of `Person` entities received from a service, loaded using a `State` with the [`Async`](xref:Uno.Extensions.Mvux.ListStates#async) factory method.
As you can gather from the code, the service interacts with an external source to load and save `Person` data. In the example below, we can see the use of two of its methods: `GetAllAsync` and `CreateNameAsync`.
As you can gather from the code, the service interacts with an external source to load and save `Person` data. In the example below, we can see the use of two of its methods: `GetAllAsync` and `CreateNameAsync.`
<<<<<<< HEAD
There's also the `CreateNewPerson` method, which gets generated as a command in the bindable proxy, to be invoked from the View (refer to [commands](xref:Uno.Extensions.Mvux.Advanced.Commands) to learn about how MVUX generates commands). This method uses `CreateRandomName`, which generates a random name. Its implementation has been removed for brevity.
=======
There's also the `CreateNewPerson` method, which gets generated as a command in the ViewModel and can be invoked from the View (refer to [commands](xref:Uno.Extensions.Mvux.Advanced.Commands) to learn about how MVUX generates commands). This method uses `CreateRandomName,` which generates a random name (implementation removed for brevity).
>>>>>>> 1119f2971 (docs: update reactive messaging)
The line using the MVUX messaging extension method is the one calling `messenger.Observe`. Read the code, and this line will be explained thereafter.
The line using the MVUX messaging extension method is the one calling `messenger.` Observe. Read the code, and this line will be explained later.
```csharp
using CommunityToolkit.Mvvm.Messaging;
@ -135,9 +153,9 @@ public partial record PeopleModel
The `Observe` method in the model code subscribes the `People` state to the messenger's entity-change messages (`EntityMessage<Person>`).
An `EntityMessage<T>` carries an `EntityChange` enum value which indicates its type of change (`Created`, `Updated`, and `Deleted`), as well as the actual entity that was changed.
An `EntityMessage<T>` carries an `EntityChange` enum value which indicates its type of change (`Created`, `Updated`, and `Deleted`) and the entity changed.
These messages are sent in the service upon successful creation of a `Person`, signaling the model to update itself with the new data. This is indeed automatically reflected in the `People` list-state, which adds the newly created `Person`.
These messages are sent in the service upon successful creation of a `Person`, signaling the model to update itself with the new data. This is automatically reflected in the `People` `ListState,` which adds the newly created `Person.`
The service's code looks like the following:
@ -226,7 +244,7 @@ They all share a common goal - to send and intercept entity-message messages to
- `Observe<TOther, TEntity, TKey>(IListState<TEntity> listState, IFeed<TOther> other, Func<TOther, TEntity, bool> predicate, Func<TEntity, TKey> keySelector)`
This overload intercepts entity-change messages from the messenger for a certain entity type but only refreshes the state when the predicate returns `true` based on related entities from another feed.
This overload intercepts entity-change messages from the messenger for a specific entity type but only refreshes the state when the predicate returns `true` based on related entities from another feed.
Using the previous example, if each `Person` has a list of `Phone` with a `Phone.PersonId` property associating them to their owning `Person`, making changes to a `Phone` (e.g., removing one), will have the service send an entity-change message which will refresh the `SelectedPersonPhones` list-state, but only if the `Phone.PersonId` matches with the currently selected person `Id`:
@ -242,7 +260,12 @@ public partial record PeopleModel
PhoneService = phoneService;
messenger.Observe(People, person => person.Id);
messenger.Observe(SelectedPersonPhones, SelectedPerson, (person, phones) => true, person => person.Id);
messenger.Observe(
SelectedPersonPhones,
SelectedPerson,
(person, phones) => true,
person => person.Id);
}
public IListState<Person> People =>
@ -254,7 +277,8 @@ public partial record PeopleModel
public IState<Person> SelectedPerson => State<Person>.Empty(this);
public IListState<Phone> SelectedPersonPhones => ListState.FromFeed(this, SelectedPerson.SelectAsync(GetAllPhonesSafe).AsListFeed());
public IListState<Phone> SelectedPersonPhones =>
ListState.FromFeed(this, SelectedPerson.SelectAsync(GetAllPhonesSafe).AsListFeed());
private async ValueTask<IImmutableList<Phone>> GetAllPhonesSafe(Person selectedPerson, CancellationToken ct)
{
@ -280,18 +304,18 @@ public partial record PeopleModel
}
```
The `SelectedPersonPhone` state will only be refreshed if it falls under the predicate criteria, which is limited to the currently selected `Person`.
The `SelectedPersonPhone` state will only be refreshed if it meets the predicate criteria, which are limited to the currently selected `Person`.
> ![NOTE]
> The `Selection` method above picks up UI selection changes and reflects them onto a state. This subject is covered [here](xref:Uno.Extensions.Mvux.Advanced.Selection).
- `Observe<TOther, TEntity, TKey>(IState<TEntity> state, IFeed<TOther> other, Func<TOther, TEntity, bool> predicate, Func<TEntity, TKey> keySelector)`
This overload is the same as the previous one, except it watches a single-item state rather than a list-state, as in the previous example.
This overload is the same as the previous one, except it watches a single-item state rather than a `ListState,` as in the last example.
### Update
The MVUX messaging package also includes a pair of `Update` methods that enable updating an `IState<T>` or an `IListState<T>` from an `EntityMessage<T>`.
The main purpose of these messages is to serve the aforementioned `Observe` extension methods, but they can be used otherwise, for example, if you would like to create additional implementations of the `Observe` extension methods.
These messages' primary purpose is to serve the aforementioned `Observe` extension methods. However, they can also be used to create additional implementations of these methods.
These methods apply data from an `EntityMessage<T>` to an `IState<T>` or an `IListState<T>`. So for example, if an entity-message contains a `created` `T` entity, applying this entity-message to a list-state will add that record to the list-state. The same applies to updating or removing.
These methods apply data from an `EntityMessage<T>` to an `IState<T>` or an `IListState<T>`. So, for example, if an entity-message contains a `created` `T` entity, applying this entity-message to a `ListState` will add that record to the `ListState`. The same applies to updating or removing.