Concepts

This section explains Specmonstah's model: the data that Specmonstah works with, and the operations you can perform on that data. You'll learn these concepts by answering the following questions:

  • How does Specmonstah generate data for database insertion?

  • How does Specmonstah insert records in the correct order?

Data Generation

Data generation happens in two phases:

  1. You add ents to an ent db's ent graph

  2. You add the data generated by clojure.spec to each ent as an ent attr during ent visitation

Let's unpack this.

Specmonstah works by generating an ent graph. Say you want to generate and insert three topics that references a topic-category, where the topics and the topic-category all reference a user. Specmonstah accomplishes this by first creating a graph like this:

In the graph above, we call the :topic, :topic-category, and :user nodes ent types and the rest ents:

Ent type. An ent type is analogous to a relation in a relational database, or a class in object-oriented programming. It differs in that relations and classes define all the attributes of their instances, whereas ent types don't. Ent types define how instances are related to each other. For example, a Todo schema might include a :description attribute, but the :todo ent type doesn't. On the other hand, the :todo ent type does specify that a :todo instances reference :todo-list instances.

Ent types are represented as nodes in the ent graph, with directed edges going from ent types to their instances. It's rare that you'll interact with ent types directly.

Ent. An ent is an instance of an ent type. Ents have names (:t0, :u0, etc), and reference other ents. They're represented as nodes in the ent graph, with directed edges going from ents to the ents they reference; there's a directed edge from :tl0 to :u0 because :tl0 references :u0. The graph's topology is used to ensure that :u0 gets inserted before :tl0.

In creating the above graph, we would say that we add ents to an ent db. An ent db is represented as a map that contains an ent graph.

The ent db also contains a schema. The schema describes how ents of different types refer to each other, and it's used to construct the directed edges between ents. You'll learn all about schemas in a later section of the tutorial.

So that's the first step of data generation: You add ents to an ent db's ent graph. After that, you add the data generated by clojure.spec to each ent as an ent attr.

For example, the ents :u0 and :tl0 are not maps. They're just a graph node, and as such they cannot be inserted in a database. Specmonstah uses clojure.spec to generate data for :u0 and :tl0 and then associates the data with :u0 and :tl0 as an ent attr. You can think of this as being represented using a map like this, with :spec-gen as the ent attr:

{:u0  {:spec-gen {:username "billy"
                  :id       1}}
 :tc0 {:spec-gen {:id       2
                  :owner-id 1
                  :title    "topic category"}}}

Ent Visitation

The process of adding ent attrs is called visitation. Visiting ent nodes is kind of like mapping: when you call map on a seq, you apply a mapping function to each element, creating a new seq from the mapping function's return values. By the same token, when you visit ents you apply a visiting function to each ent. The visiting function's return value is stored as an attribute on the ent - in this case, :spec-gen.

Visitation happens in reverse topologically sorted order, meaning that since :tc0 has a directed edge pointing to :u0, the visiting function is applied to :u0 before :tc0. This is how the spec data generating visiting function is able to correctly set :owner-id to 1:

  1. The visiting function is applied to :u0, generating the :id 1

  2. The visiting function is applied to :tl0. It's able to use the

    edge from :tl0 to :u0 to look up :u0's :id and set that

    as the value for :owner-id.

The same visitation process that's used to generate data with clojure.spec is also used to insert records in a database. The insertions happen in the correct order, satisfying foreign key constraints.

One more note: When you play with Specmonstah in a REPL, you'll notice that it generates a lot of data. Specmonstah provides a bunch of functions that project different views of the ent db so that you can focus just on whatever's relevant for you. For example, sm/view will create an image of the ent graph - it's what I used to create the image at the beginning of this page.

That covers the main data structures and operations:

  • Data

    • ent db

      • schema

      • ent graph

      • ent attrs

  • Operations

    • add ents

    • add ent attrs (ent visitation)

    • create views of the ent graph

Now that you have the broad picture of how Specmonstah works, let's start exploring the details with source code. The rest of the tutorial consists of numbered sections with corresponding clojure files under the examples directory, each introducing new concepts. You'll have the best experience if you follow along in a REPL.

Last updated