We've built a stand-alone client application. The goal of Correspondence, however, is to build occasionally connected client applications. This lesson is where we begin.

A client publishes facts to a queue, and subscribes to receive facts from the queue. This allows clients to collaborate without being directly connected. Facts sent by the publisher will be received by the subscriber.

Correspondence is different from other publish/subscribe systems that you might have used. Other systems have separate concepts for messages and queues. In Correspondence, every fact is both a message and a queue.

When I log on as the user "alan1", I am interested in all facts related to "alan1". The User("alan1") is a queue. Successors, like a Player in a Game, are published to that queue.

To publish a fact, add the publish keyword to the model.
fact Player {
key:
    publish User user;
    Game game;

query:
    bool isActive {
        not exists Outcome o : o.game = this.game
    }
}

Click "Transform All Templates" after making this change.

The publish keyword decorates the predecessor that represents the queue. Any client subscribing to that predecessor will receive the published fact. To subscribe to a fact, add a .Subscribe() clause to the Community. You can find this in the SynchronizationService in the application.
_community = new Community(new MemoryStorageStrategy())
    .AddAsynchronousCommunicationStrategy(new POXClientCommunicationStrategy())
    .Register<Model.CorrespondenceModel>()
    .Subscribe(() => _machine.LogOns.Select(l => l.User));
This .Subscribe() clause subscribes to all logged in users on this machine.

To configure an endpoint for publish and subscribe requests, add a communication strategy to the Community. The code above adds a POXClientCommunicationStrategy. This communication strategy calls an HTTP XML endpoint to send and receive facts from a server. All clients using this same server will share facts according to their own subscriptions. You can add as many synchronization strategies to a Community as you need.

To send and receive the messages, call the Community.BeginSynchronize() method. SynchronizationService calls this method whenever a fact is added.

To unit test collaboration between two machines, create two Communities. Give them both a shared MemoryCommunicationStrategy.
[TestClass]
public class RemoteGameTest
{
    private Community _localCommunity;
    private Community _remoteCommunity;
    private User _alan;
    private User _flynn;

    [TestInitialize]
    public void Initialize()
    {
        MemoryCommunicationStrategy sharedCommunicationStrategy = new MemoryCommunicationStrategy();
        _localCommunity = new Community(new MemoryStorageStrategy())
            .AddCommunicationStrategy(sharedCommunicationStrategy)
            .Register<Model.CorrespondenceModel>()
            .Subscribe(() => _alan);
        _remoteCommunity = new Community(new MemoryStorageStrategy())
            .AddCommunicationStrategy(sharedCommunicationStrategy)
            .Register<Model.CorrespondenceModel>()
            .Subscribe(() => _flynn);
        _alan = _localCommunity.AddFact(new User("alan1"));
        _flynn = _remoteCommunity.AddFact(new User("flynn1"));
    }
}
At first, messages do not flow from one to the other.
[TestMethod]
public void WhenOffLineGameDoesNotTransfer()
{
    _alan.Challenge("flynn1");

    Assert.IsFalse(_flynn.Players.Any());
}
To make the messages flow, call Synchronize() on both Communities. Keep calling Synchronize() until they both return false, indicating that no more messages have been sent or received.
[TestMethod]
public void WhenAlanChallengesFlynnFlynnSeesGame()
{
    _alan.Challenge("flynn1");
    Synchronize();

    Assert.IsTrue(_flynn.Players.Any());
}

private void Synchronize()
{
    // Continue to synchronize until both are stable.
    while (_localCommunity.Synchronize() || _remoteCommunity.Synchronize());
}
Facts flow in both directions.
[TestMethod]
public void WhenFlynnChallengesAlanAlanSeesGame()
{
    _flynn.Challenge("alan1");
    Synchronize();

    Assert.IsTrue(_alan.Players.Any());
}
Subscribers to a fact don't just receive direct successors. They receive everything in the fact tree. All direct or indirect successors of the published fact are also published.

For example, take another look at the Outcome of a game.
fact Outcome {
key:
    Game game;
    Player winner;
}

This fact is not directly published. But it is the successor of a Player, which is published to a User. Because of this, a subscriber to a User can see the Outcome of every game in which he is the winner.
[TestMethod]
public void WhenAlanResignsFlynnNoLongerSeesGame()
{
    _alan.Challenge("flynn1");
    Synchronize();
    Player player = _alan.Players.Single();
    Player opponent = player.Game.Players.Single(p => p != player);
    player.Game.DeclareWinnner(opponent);
    Synchronize();

    Assert.IsFalse(_flynn.Players.Any());
}

Next: Lesson 4b: Correspondence server

Last edited May 15, 2011 at 5:34 AM by MichaelLPerry1971, version 2

Comments

No comments yet.