Defining a Starting Point

Every specification has a starting point. This is usually one fact, sometimes two, but rarely more. The first step to writing a Jinaga specification is to identify the starting point.

For the first few queries of an application, the starting point will almost always be a user. This is often the logged-in user, but sometimes it's a pre-defined user. Let's look at each of these in turn.

The Logged-In User

For an app that records personal information, the logged-in user is the best starting point. You can find this fact by calling the Login function.

// The user is a fact of type Jinaga.User and has a public key.
// The profile is a class with a display name property
// determined by the authentication provider.
var (user, profile) = await jinagaClient.Login();

Once you know the logged-in user, you can query for the resources owned by that user. This will be a query for successors.

For example, suppose that you were presenting albums for a photo management app. The model might look like this:

%0 Album Album Jinaga.User Jinaga.User Album->Jinaga.User creator

The specification would start from the user and match the albums.

var albumbsByUser = Given<User>.Match((user, facts) =>
    from album in facts.OfType<Album>()
    where album.creator == user
    select album);

A Pre-Defined User

For an enterprise app, the starting point is often a pre-defined user. When the application is first deployed, administrators generate a key pair to initialize the environment. That public key then becomes part of the identity of the environment.

For example, suppose an enterprise application needed to list all of the customers in an environment.

%0 Customer Customer Environment Environment Customer->Environment environment Jinaga.User Jinaga.User Environment->Jinaga.User creator

For this application, operators store the pre-defined user's public key and the environment name in configuration variables. Those constants are delivered to the client app on page load.

var publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZ";
var environmentName = "Production";

Then we can generate the starting point for the initial queries.

var environment = new Environment(new User(publicKey), environmentName);

From there, we can query for successors.

var customersInEnvironment = Given<Environment>.Match((environment, facts) =>
    from customer in facts.OfType<Customer>()
    where customer.environment == environment
    select customer);

Starting from Two Facts

In some situations, we will combine these two techniques and start a specification from two facts. This happens most often when a user logs into an enterprise application.

Let's take the example where a user logs into the enterprise app to place an order. The first query will need to find the customers for which the user is a buyer. The model looks like this:

%0 Customer.Buyer Customer.Buyer Customer Customer Customer.Buyer->Customer customer Jinaga.User Jinaga.User Customer.Buyer->Jinaga.User buyer Environment Environment Customer->Environment environment Environment->Jinaga.User creator

We would write a specification starting from two facts: the environment and the logged-in user. The specification would look for matching customer buyers.

var customerBuyersInEnvironmentForUser = Given<Environment, User>.Match((environment, user, facts) =>
    from buyer in facts.OfType<CustomerBuyer>()
    where buyer.customer.environment == environment
    where buyer.buyer == user
    select buyer);

This gives the user a subset of the environment to which they have logged in.

Continue With

Tracing a Path

Jinaga is a product of Jinaga LLC.

Michael L Perry, President