Steps

Facts can represent steps in a workflow. For each step, the fact captures all of the context that is relevant to that particular decision. It refers back to prior workflow steps to create chains of operations.

The creator of a blog can write posts in private. Once they are satisfied, they can publish the post. They will record this decision to publish using a new fact. This fact will capture the specific title and content that the author chose to publish. Finally, it will refer back to prior published versions so that they can be replaced.

[FactType("Blog.Post.Publish")]
public record PostPublish(Post post, PostTitle title, PostContent content, PostPublish[] prior) { }

Renderer.RenderTypes(typeof(PostPublish))
%0 Blog.Post.Publish Blog.Post.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

When the user makes the decision to publish the post, we capture that as a fact.

var post1Publish0 = await jinagaClient.Fact(new PostPublish(post1, postTitle1, postContent1, []));

jinagaClient.RenderFacts(post1Publish0)
%0 eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== Jinaga.User publicKey --- FAKE USER --- fRStq94kRaFpm+n+5BCGNRP7e7NOyihLThCOo0vRo+inA0LbPUbVkJsNJwcWCKIVb9lUylMwc7Bt2vfHTbicPg== Blog.Site createdAt 2024-05-18T20:32:02.... fRStq94kRaFpm+n+5BCGNRP7e7NOyihLThCOo0vRo+inA0LbPUbVkJsNJwcWCKIVb9lUylMwc7Bt2vfHTbicPg==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== creator 8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw== Blog.Post createdAt 2024-05-18T20:32:03.... 8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== author 8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw==->fRStq94kRaFpm+n+5BCGNRP7e7NOyihLThCOo0vRo+inA0LbPUbVkJsNJwcWCKIVb9lUylMwc7Bt2vfHTbicPg== site AMe9xpS8GHaCTkyBmnvHESY48H7xtHshMrpi5kjUYaoJZTf9LavJdtFr3DR8pBzC1z3UJtBcxe+eCqd0qqG5Qw== Blog.Post.Title value Interesting Facts AMe9xpS8GHaCTkyBmnvHESY48H7xtHshMrpi5kjUYaoJZTf9LavJdtFr3DR8pBzC1z3UJtBcxe+eCqd0qqG5Qw==->8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw== post petnLBBgNIGTKz+tPUctalOq0LgKZC7zfsehdIYy1mG0tQZZKWACchVY/U5CKfogTLgQr19x5k7AN9DUqKU0GQ== Blog.Post.Content markdown Let me tell you abou... petnLBBgNIGTKz+tPUctalOq0LgKZC7zfsehdIYy1mG0tQZZKWACchVY/U5CKfogTLgQr19x5k7AN9DUqKU0GQ==->8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw== post Q8BZQrwz1UvD1F/SoTzXIKrYp+U1Rb2XCZZarkQjJ4Mvq2CBW/GJxPdgNYJ20kCOCfaaw77Tg4PrSpoK+HO0EQ== Blog.Post.Publish Q8BZQrwz1UvD1F/SoTzXIKrYp+U1Rb2XCZZarkQjJ4Mvq2CBW/GJxPdgNYJ20kCOCfaaw77Tg4PrSpoK+HO0EQ==->8Cl5MsQaiRie2EbqjNUhj5Jf8TjJ8e/0O1+Knfsw3pyIHKTaaJ2okiPxoDtGEztPS3MXQiDMV1G8jCC5G0mvbw== post Q8BZQrwz1UvD1F/SoTzXIKrYp+U1Rb2XCZZarkQjJ4Mvq2CBW/GJxPdgNYJ20kCOCfaaw77Tg4PrSpoK+HO0EQ==->AMe9xpS8GHaCTkyBmnvHESY48H7xtHshMrpi5kjUYaoJZTf9LavJdtFr3DR8pBzC1z3UJtBcxe+eCqd0qqG5Qw== title Q8BZQrwz1UvD1F/SoTzXIKrYp+U1Rb2XCZZarkQjJ4Mvq2CBW/GJxPdgNYJ20kCOCfaaw77Tg4PrSpoK+HO0EQ==->petnLBBgNIGTKz+tPUctalOq0LgKZC7zfsehdIYy1mG0tQZZKWACchVY/U5CKfogTLgQr19x5k7AN9DUqKU0GQ== content

To render the site, create a specification that displays only the published posts.

Here you have to be careful. You might at first want to write a specification that selects predecessors and properties of predecessors like this:

var publishedPostsInSiteIncorrect = 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))
    select new
    {
        // Get the post predecessor from the publish fact
        Post = publish.post,
        // Get the value of the title predecessor
        Title = publish.title.value,
        // Get the value of the content predecessor
        Content = publish.content.markdown
    });
Error: Jinaga.SpecificationException: Cannot select post directly. Give the fact a label first.
at Jinaga.Repository.SpecificationProcessor.ProcessProjection...

That is an invalid specification. The problem is that we are trying to select a predecessor directly in the projection. We can't know for sure that just one predecessor exists. The model may have changed over time. That predecessor might have been added, or it might have changed from an array to a single value. Jinaga does not make any assumptions about the evolution of the model.

Instead, you have to explicitly give each predecessor a label. Then, you can select fields of those predecessors in the projection.

var 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))
    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
    });

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

publishedPosts

If the publish fact does not have a title or content predecessor, then this specification will not return the result. Furthermore, if it has multiple title or content predecessors -- for example if the model previously captured arrays instead of single values -- then it would return one result per combination. This might not be your desired result.

An alternate specification might explicitly say that it wants one result per published post, no matter how many title or content facts are present. It then gets the list of titles and contents within the projection. If multiples exist, then they will be listed in these collections.

var publishedPostsInSiteAlternate = 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))
    from post in facts.OfType<Post>()
    where post == publish.post
    select new
    {
        Post = post,
        Titles =
            from title in facts.OfType<PostTitle>()
            where title == publish.title
            select title.value,
        Contents =
            from content in facts.OfType<PostContent>()
            where content == publish.content
            select content.markdown
    });

var publishedPostsAlternate = await jinagaClient.Query(publishedPostsInSiteAlternate, site);

publishedPostsAlternate

Jinaga does not assume that you want one form vs the other. You must explicitly label the facts that you want to be present in the projection. From there, you can define nested projections to pick out related facts.

Continue With

Captured Versions

Jinaga is a product of Jinaga LLC.

Michael L Perry, President