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.
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:
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);
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.
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);
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:
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.