Concepts
Last updated
Last updated
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 happens in two phases:
You add ents to an ent db's ent graph
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 topic
s that references a topic-category
, where the topic
s 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:
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
:
The visiting function is applied to :u0
, generating the :id
1
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.