Additional Workflow Steps

Workflow often includes more than a single step. To represent additional steps in a workflow, define more fact types. Each step refers back to the previous step in the workflow.

After a post is published, it can be rescinded. The author might want to take it down for a time, or permanently remove it. Represent this with another fact type.

[FactType("Blog.Post.Rescind")]
public record PostRescind(PostPublish publish, DateTime rescindedAt) { }

Renderer.RenderTypes(typeof(PostRescind))
%0 Blog.Post.Rescind Blog.Post.Rescind Blog.Post.Publish Blog.Post.Publish Blog.Post.Rescind->Blog.Post.Publish publish Blog.Post.Publish->Blog.Post.Publish prior Blog.Post Blog.Post Blog.Post.Publish->Blog.Post post Blog.Post.Title Blog.Post.Title Blog.Post.Publish->Blog.Post.Title title Blog.Post.Content Blog.Post.Content Blog.Post.Publish->Blog.Post.Content content Blog.Site Blog.Site Blog.Post->Blog.Site site Jinaga.User Jinaga.User Blog.Post->Jinaga.User author Blog.Post.Title->Blog.Post post Blog.Post.Title->Blog.Post.Title prior Blog.Post.Content->Blog.Post post Blog.Post.Content->Blog.Post.Content prior Blog.Site->Jinaga.User creator

Modify the specifications that should take these workflow steps into account. For example, we don't want to display rescinded posts on the site.

publishedPostsInSite = Given<Site>.Match((site, facts) =>
    from publish in facts.OfType<PostPublish>()
    where publish.post.site == site
    where !facts.Any<PostPublish>(next => next.prior.Contains(publish))
    // Filter out the rescinded posts
    where !facts.Any<PostRescind>(pr => pr.publish == publish)
    from post in facts.OfType<Post>()
    where post == publish.post
    from title in facts.OfType<PostTitle>()
    where title == publish.title
    from content in facts.OfType<PostContent>()
    where content == publish.content
    select new
    {
        Post = post,
        Title = title.value,
        Content = content.markdown
    });

Now if the author rescinds their publication, the post will no longer appear on the site.

var rescindPost1Publish1 = await jinagaClient.Fact(new PostRescind(
    post1Publish1, DateTime.UtcNow));

publishedPosts = await jinagaClient.Query(publishedPostsInSite, site);

publishedPostsViewModel = publishedPosts.Select(p => new
{
    Title = p.Title,
    Content = p.Content
});

publishedPostsViewModel
(empty)

The next step in the workflow might be to re-publish a rescinded post. As you might expect, we represent that with another fact.

[FactType("Blog.Post.Republish")]
public record PostRepublish(PostRescind rescind) { }

Renderer.RenderTypes(typeof(PostRepublish))
%0 Blog.Post.Republish Blog.Post.Republish Blog.Post.Rescind Blog.Post.Rescind Blog.Post.Republish->Blog.Post.Rescind rescind Blog.Post.Publish Blog.Post.Publish Blog.Post.Rescind->Blog.Post.Publish publish Blog.Post.Publish->Blog.Post.Publish prior Blog.Post Blog.Post Blog.Post.Publish->Blog.Post post Blog.Post.Title Blog.Post.Title Blog.Post.Publish->Blog.Post.Title title Blog.Post.Content Blog.Post.Content Blog.Post.Publish->Blog.Post.Content content Blog.Site Blog.Site Blog.Post->Blog.Site site Jinaga.User Jinaga.User Blog.Post->Jinaga.User author Blog.Post.Title->Blog.Post post Blog.Post.Title->Blog.Post.Title prior Blog.Post.Content->Blog.Post post Blog.Post.Content->Blog.Post.Content prior Blog.Site->Jinaga.User creator

Then we update the specification to ignore a rescind fact if it has been republished.

publishedPostsInSite = Given<Site>.Match((site, facts) =>
    from publish in facts.OfType<PostPublish>()
    where publish.post.site == site
    where !facts.Any<PostPublish>(next => next.prior.Contains(publish))
    where !facts.Any<PostRescind>(pr => pr.publish == publish &&
        // Include only the rescinds that have not been republished
        !facts.Any<PostRepublish>(rp => rp.rescind == pr))
    from post in facts.OfType<Post>()
    where post == publish.post
    from title in facts.OfType<PostTitle>()
    where title == publish.title
    from content in facts.OfType<PostContent>()
    where content == publish.content
    select new
    {
        Post = post,
        Title = title.value,
        Content = content.markdown
    });

And with this, the author can re-publish their rescinded post.

var republishPost1Rescind = await jinagaClient.Fact(new PostRepublish(
    rescindPost1Publish1));

publishedPosts = await jinagaClient.Query(publishedPostsInSite, site);

publishedPostsViewModel = publishedPosts.Select(p => new
{
    Title = p.Title,
    Content = p.Content
});

publishedPostsViewModel

The decision of which workflow facts have timestamps is very deliberate. It must be considered carefully. A timestamp is added to a workflow fact only when it is needed to differentiate it from other facts.

Let's first consider the PostPublish fact. It has no timestamp. The reason is that it contains a prior array that places it in history relative to other PostPublish facts. This is enough to distinguish it from other publication steps.

Then let's look at the PostRescind fact. This one has a timestamp. Without this differentiator, there could be only one PostRescind for a given PostPublish. An author could rescind a post and then re-publish it. After that, they could not express the desire to rescind it again. That second rescind would be indistinguishable from the first.

Finally, let's look at PostRepublish. This one does not have a timestamp. It does not need one because it is the last step in the workflow. If an author wishes to rescind the publication again, they could create another PostRescind fact. There is no need to further qualify a re-publish fact, and therefore no need to differentiate it from any other re-publication of the same rescinded post.

Continue With

LINQ Guidance

Jinaga is a product of Jinaga LLC.

Michael L Perry, President