2. Convert to code
With your initial list of expectations created, it is now time to convert them to code.
Scenario
Pretend that you are running a blogging platform where authors can publish posts to their own blogs.
The blog posts are reachable via the URL blogname.com/name-of-blog-post
.
For this to work the names of the blog posts have to be unique.
This leads us to the expectation: Authors can not publish two blog posts with the same name.
Identify relevant tables and columns
The expectation makes statements about authors, blog posts and their connection. To model them you need to identify which tables and columns are involved.
CREATE TABLE authors (
id UUID PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE posts (
id UUID PRIMARY KEY,
name text NOT NULL,
author_id UUID NOT NULL REFERENCES authors (id)
);
In this scenario only the posts table is relevant to us, because it contains the names of the posts and connects them to the relevant author, which is all we need for our expectation. We do not care about other information such as the name of the author in this case.
Preparing your environment
Familiarity with C# required
For the following steps a basic familarity with C# is required. If you are not, take a look at Learn to code C# and .NET In-Browser Tutorial. You only need the basics of methods, classes and the built-in data types.
You need a .NET 7.0 or later C# console project to house the code defining your assertions.
- Create a new console project to house your definition
- Create a new folder
- Open it in a terminal
- Run
dotnet new console --name Definition --output ./
- Install the latest version of the
DataConformance.Checking
package (nuget.org)- Open the project folder in a terminal
- Run
dotnet add package DataConformance.Checking
- Replace the Program.cs file with below version
- Run the project
- Open the project folder in a terminal
- Run
dotnet run
using DataConformance.Checking;
new DefinitionExporter()
.AddAssembly(typeof(Program).Assembly)
.ExportToFile("./definition.json");
The project should run without exception and a definition.json file should have been created.
Model entities
To make the posts table available to our code, we have to model it as an entity. Every entity contains a reference to the table it is modeling, its relevant columns and the list of columns making up the primary key.
For the posts table we end up with below entity definition:
using DataConformance.Checking.Entities;
public class Post : Entity
{
public override TableReference TableReference => new("posts");
public override IReadOnlyCollection<ColumnReference>
PrimaryKeyColumns => new[] { Id };
public ColumnReference Id => ColumnReference.String(this, "id");
public ColumnReference AuthorId => ColumnReference.String(this, "author_id");
public ColumnReference Name => ColumnReference.String(this, "name");
}
Creating the assertion
Our expectation Authors can not publish two blog posts with the same name. means that when we look at any combination of two different blog posts by the same author they can not have the same name.
We can express this with code as follows:
// Add to other using statements
using DataConformance.Checking;
using DataConformance.Checking.Constraints;
// Add below last ColumnReference
[Assertion]
public Constraint AuthorsCanNotPublishTwoBlogPostsWithTheSameName()
{
return Check.For(Times.All, (Post postA) =>
Check.For(Times.All, (Post postB) =>
postA.IsNotEqualTo(postB)
.And(postA.AuthorId.IsEqualTo(postB.AuthorId))
.Implies(postA.Name.IsNotEqualTo(postB.Name))
)
);
}
using DataConformance.Checking;
using DataConformance.Checking.Constraints;
using DataConformance.Checking.Entities;
public class Post : Entity
{
public override TableReference TableReference => new("posts");
public override IReadOnlyCollection<ColumnReference>
PrimaryKeyColumns => new[] { Id };
public ColumnReference Id => ColumnReference.String(this, "id");
public ColumnReference AuthorId => ColumnReference.String(this, "author_id");
public ColumnReference Name => ColumnReference.String(this, "name");
[Assertion]
public Constraint AuthorsCanNotPublishTwoBlogPostsWithTheSameName()
{
return Check.For(Times.All, (Post postA) =>
Check.For(Times.All, (Post postB) =>
postA.IsNotEqualTo(postB)
.And(postA.AuthorId.IsEqualTo(postB.AuthorId))
.Implies(postA.Name.IsNotEqualTo(postB.Name))
)
);
}
}
With the two nested Check.For(Times.All, ...
we generate all possible combinations of two blog posts.
First we need to check that they are not actually the same post with postA.IsNotEqualTo(postB)
as they would of course have the same name and author.
Then we check that they are by the same author with postA.AuthorId.IsEqualTo(postB.AuthorId)
.
Only if two posts are not the same and are by the same author, we assert that they don't have the same name with postA.Name.IsNotEqualTo(postB.Name)
.
Check out the docs for the DataConformance.Checking package to learn more about writing assertions.
Exporting your definition
While you use code to define your assertions, that code can not be directly uploaded to DataConformance. Instead the DefinitionExporter called in your Program.cs file created earlier executes your code and generates a definition.json file from it. This file can then be uploaded to and processed by DataConformance.
Run your project and locate the generated definition.json file. In the next step you will be shown where to upload it.
Conclusion
We have successfully converted the expectation to code. DataConformance can use the constraint generated by the code to find any data in the database that violates it. In the next step, testing against reality, you will set up DataConformance to actually run your assertions against your database.