Conflicts

The advantage of modeling mutable properties using this pattern is that it detects concurrent edits, often called conflicts. Two people can change the same mutable property on different machines. After the concurrent edit, the model reflects that the situation arose, and identifies the different values.

Recall what the history of the blog change looked like. This graph tells us that the name of the site is "My Journal", since that is the end of the chain.

jinagaClient.RenderFacts(names)
%0 eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== Jinaga.User publicKey --- FAKE USER --- G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== Blog.Site createdAt 2024-06-14T01:36:26.... G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== creator rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== Blog.Site.Name value My Site rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== Blog.Site.Name value My Blog IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== prior bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A== Blog.Site.Name value My Journal bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior

If someone else was editing the blog name while we were setting it to "My Journal", then they would have created a different fact.

var siteName2a = await jinagaClient.Fact(new SiteName(site, "Shared Blog", [siteName1]));

This is the graph as they see it.

jinagaClient.RenderFacts(siteName2a)
%0 eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== Jinaga.User publicKey --- FAKE USER --- G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== Blog.Site createdAt 2024-06-14T01:36:26.... G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== creator rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== Blog.Site.Name value My Site rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== Blog.Site.Name value My Blog IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== prior fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA== Blog.Site.Name value Shared Blog fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior

Once both you and they share their facts, both of you can see the new results.

names = await jinagaClient.Query(namesOfSite, site);

jinagaClient.RenderFacts(names)
%0 eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== Jinaga.User publicKey --- FAKE USER --- G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== Blog.Site createdAt 2024-06-14T01:36:26.... G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== creator rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== Blog.Site.Name value My Site rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== Blog.Site.Name value My Blog IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== prior bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A== Blog.Site.Name value My Journal bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA== Blog.Site.Name value Shared Blog fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior

The chain has become a graph. This graph has two leaves, indicating that a concurrent edit has taken place. And those two leaves are the possible values.

Furthermore, the results of the query are just the leaves of the graph. No fancy code is required to find the leaves of the graph. Just count the elements.

names.Count()

2

Those elements are the candidate values.

names.Select(n => n.value)

[ My Journal, Shared Blog ]

Conflict Resolution

The application can display an indicator that a concurrent edit has taken place. Furthermore, it can present the candidate values. When you or your co-editor discover the issue, it is simple to correct. The application creates a new fact with both candidates as predecessors.

string desiredName = "My Journal";

if (names.Count() != 1 || names.Single().value != desiredName)
{
    await jinagaClient.Fact(new SiteName(site, desiredName, names.ToArray()));
}

The code pattern for conflict resolution looks a little complicated, so let's break it down.

The user has just finished their editing experience. The desiredName property contains the value that the user entered or selected. Then the application has to determine what to do with that value.

If the list of candidate names doesn't have one value, then we have a concurrent edit. The application needs to record a new SiteName to resolve the conflict.

If the list of candidate names has exactly one element, then it checks the value. If it's different from the desired value, then it records the change. Otherwise, the value is already equal, and therefore does not need to change.

After this code executes, the graph looks like this:

names = await jinagaClient.Query(namesOfSite, site);

jinagaClient.RenderFacts(names)
%0 eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== Jinaga.User publicKey --- FAKE USER --- G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== Blog.Site createdAt 2024-06-14T01:36:26.... G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw==->eLG0D2gojV+51Ix80JNo2Uh4EQTKvjbZUq5JNrf9K5gSXEqRmi7LBSleOl09F/bWdrlWUgDkalEH847v83U7fA== creator rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== Blog.Site.Name value My Site rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== Blog.Site.Name value My Blog IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw==->rHON+B4BVoUVolwG+Luqto8urdCbYRlf6ufGY1OoqmsqHweiRE5Y781ObG0l6Fo5hySqDWZBJ1o7JESFtxJ2ow== prior bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A== Blog.Site.Name value My Journal bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA== Blog.Site.Name value Shared Blog fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA==->IAuG7KTsZUgPqg0OPu9MvCCx4Idx6pfZZRIoaZEw26Q4ftRIQpPudPIW6TM1vjDSVrIuG5S574yRd8WVOqBSmw== prior MbB9niB36GkK/QBj0A9gIMo8qOnzy60PlK39wXrS7WQu7rCvm8Cb5li0tKWN2h3SLJbwbYKm5L2P+1Uf1blOSQ== Blog.Site.Name value My Journal MbB9niB36GkK/QBj0A9gIMo8qOnzy60PlK39wXrS7WQu7rCvm8Cb5li0tKWN2h3SLJbwbYKm5L2P+1Uf1blOSQ==->G45DK5pN3oktzWVbxKiVMOs3I7JR2s1L2ZGbP4cSqTbW5sAWgQMDOE8EmD7rec74AVnDe4tRWmLlK8C4BU2ztw== site MbB9niB36GkK/QBj0A9gIMo8qOnzy60PlK39wXrS7WQu7rCvm8Cb5li0tKWN2h3SLJbwbYKm5L2P+1Uf1blOSQ==->bz24dzLlzthxULmSBFity/Htzd0T8C8svo1WVj/4pXNf7RdUSmfSAzaeXM841j/ATBZK9lLoiJ+IIwQhU/eE2A== prior MbB9niB36GkK/QBj0A9gIMo8qOnzy60PlK39wXrS7WQu7rCvm8Cb5li0tKWN2h3SLJbwbYKm5L2P+1Uf1blOSQ==->fqqN3k5jOpuxHKrcwWdK1vZ7knan7bxCrwBg6I0izECAcF86L3O6UNlBaZsd7Bg5HmBnQ+QhNG061s49tCsMiA== prior

Notice that the graph again has only one leaf. It has been merged back to a single value. The conflict has been resolved.

When setting up the editing experience for mutable properties, run the query before the user begins editing. You will want to freeze the prior values for mutable properties before they begin. Otherwise, the user might overwrite someone else's change without ever seeing it.

To avoid a noticeable pause as the editor appears, use jinagaClient.Local.Query to initialize the candidate list. This queries the local store without connecting to the remote replicator. Presumably, the information the user wants to edit has already been pulled down to render a previous view.

Continue With

Projections

Jinaga is a product of Jinaga LLC.

Michael L Perry, President