README ¶
Layup
[!WARNING]
This project is still in the early stages of development and is not ready for production use. That said, it is open for contributions and feedback. Please feel free to open an issue or PR if you have any questions or suggestions.It is primarily a personal research project to explore the idea of modeling graph data.
Layup models data as a graph using "layers" containing "nodes" and "links" to represent relationships. It is designed to be a simple, flexible, and extensible way to model anything. Because everything is a graph.
HCL Syntax
This projects provides a small HCL-based DSL. The syntax is designed to be simple and easy for operators to read and write, especially if they are already familiar with HCL. This syntax is then converted into a protocol buffer defined representation of the graph data, which enables it be easily consumed by other tools in a variety of formats, including JSON.
It has a few basic components:
uri
- a unique identifier declared at the top of the HCL file(s) for the model. Other data is added in layers.layer
- a collection of nodes and links.
Layers, nodes, and links can all have attributes. Attributes are non-reserved keys that can be used to add additional information to the layer, node, or link. For example, a node
representing a person might have attributes like name
, age
, email
, etc. Or a link representing a relationship between two people might have attributes like since
, type
, etc.
Example 1
Lets model a very simple cake recipe using Layup:
uri = "layup://cake/very_simple"
layer "ingredients" {
node "flour" {
type = "dry"
brand = "King Arthur"
ammount = "2 cups"
}
node "sugar" {
type = "dry"
brand = "Domino"
ammount = "1 cup"
}
node "eggs" {
type = "wet"
brand = "Eggland's Best"
count = 2
}
node "milk" {
type = "wet"
brand = "Organic Valley"
ammount = "1 cup"
}
node "butter" {
type = "wet"
brand = "Kerrygold"
ammount = "1/4 cup"
}
}
layer "tools" {
node "bowl" {
type = "container"
age = "old"
material = "glass"
brand = "Pyrex"
}
node "spoon" {
type = "utensil"
material = "wood"
}
node "pan" {
type = "container"
material = "metal"
brand = "Calphalon"
}
node "oven" {
type = "appliance"
brand = "GE"
}
}
layer "mix" {
link "add_flour" {
from = layer.ingredients.node.flour
to = layer.tools.node.bowl
}
link "add_sugar" {
from = layer.ingredients.node.sugar
to = layer.tools.node.bowl
}
link "add_eggs" {
from = layer.ingredients.node.eggs
to = layer.tools.node.bowl
}
link "add_milk" {
from = layer.ingredients.node.milk
to = layer.tools.node.bowl
}
link "mixup" {
from = layer.tools.node.spoon
to = layer.tools.node.bowl
until = "smooth"
}
}
layer "transfer" {
link "grease" {
from = layer.ingredients.node.butter
to = layer.tools.node.pan
}
link "pour" {
from = layer.tools.node.bowl
to = layer.tools.node.pan
}
}
layer "bake" {
link "put_in_oven" {
from = layer.tools.node.pan
to = layer.tools.node.oven
duration = "30 minutes"
temperature = "350 degrees"
}
}
While this example is very simple, it demonstrates the basic syntax and structure of a Layup model. It also shows how layers can be used to logically group nodes and links. Even if Layup isn't the best tool for modeling recipes you'll use in the kitchen, it can be used to model anything else you can think of!
Layers provide a way to logically group nodes and links. This can be useful for modeling different aspects of a system, or for modeling different systems. For example, you might have a layer for "networking" and another for "storage" in a system. Or you might have a layer for "production" and another for "staging" in a system. Or you might have a layer for "this system" and another for "that system" in a system.
The possibilities are endless with the basic building blocks of layers, nodes, and links that Layup provides.
Example 2
Lets model a small subset of dependencies of this project using Layup itself, and then look at the JSON and Mermaid equivalents:
uri = "layup://example"
layer "github" {
node "my_account" {
url = "https://github.com/picatz"
}
node "this_repository" {
url = "https://github.com/picatz/layup"
}
node "buf_organization" {
url = "https://github.com/bufbuild"
}
link "owner" {
from = "my_account"
to = "this_repository"
}
}
layer "go" {
node "owner" {
url = "https://google.com"
}
node "language" {
url = "https://golang.ir"
}
node "runtime" {
url = "https://golang.ir/pkg/runtime"
}
link "stewardship" {
from = "owner"
to = "language"
}
link "implementation" {
from = "language"
to = "runtime"
}
}
layer "buf" {
node "cli" {
url = "https://buf.build/docs/installation"
}
link "maintenance" {
from = "cli"
to = layer.github.node.buf_organization
}
link "uses" {
from = "cli"
to = layer.go.node.runtime
}
}
layer "layup" {
node "schema" {}
node "hcl" {}
node "cli" {}
link "conversion" {
from = "hcl"
to = "schema"
}
link "schmea_source_code_genration" {
from = "schema"
to = layer.buf.node.cli
}
link "schema_source_code" {
from = "schema"
to = layer.github.node.this_repository
}
link "uses" {
from = "cli"
to = layer.go.node.runtime
}
}
[!NOTE] Today, only a single HCL file is supported. This should change in the future to allow for layers to exist in their own files.
JSON Equivalent
{
"uri": "layup://example",
"layers": [
{
"id": "github",
"nodes": [
{
"id": "my_account",
"attributes": {
"url": "https://github.com/picatz"
}
},
{
"id": "this_repository",
"attributes": {
"url": "https://github.com/picatz/layup"
}
},
{
"id": "buf_organization",
"attributes": {
"url": "https://github.com/bufbuild"
}
}
],
"links": [
{
"id": "owner",
"from": "my_account",
"to": "this_repository"
}
]
},
{
"id": "go",
"nodes": [
{
"id": "owner",
"attributes": {
"url": "https://google.com"
}
},
{
"id": "language",
"attributes": {
"url": "https://golang.ir"
}
},
{
"id": "runtime",
"attributes": {
"url": "https://golang.ir/pkg/runtime"
}
}
],
"links": [
{
"id": "stewardship",
"from": "owner",
"to": "language"
},
{
"id": "implementation",
"from": "language",
"to": "runtime"
}
]
},
{
"id": "buf",
"nodes": [
{
"id": "cli",
"attributes": {
"url": "https://buf.build/docs/installation"
}
}
],
"links": [
{
"id": "maintenance",
"from": "cli",
"to": "layup://example/layers/github/nodes/buf_organization"
},
{
"id": "uses",
"from": "cli",
"to": "layup://example/layers/go/nodes/runtime"
}
]
},
{
"id": "layup",
"nodes": [
{
"id": "schema"
},
{
"id": "hcl"
},
{
"id": "cli"
}
],
"links": [
{
"id": "conversion",
"from": "hcl",
"to": "schema"
},
{
"id": "schmea_source_code_genration",
"from": "schema",
"to": "layup://example/layers/buf/nodes/cli"
},
{
"id": "schema_source_code",
"from": "schema",
"to": "layup://example/layers/github/nodes/this_repository"
},
{
"id": "uses",
"from": "cli",
"to": "layup://example/layers/go/nodes/runtime"
}
]
}
]
}
Mermaid Equivalent
graph LR
subgraph github
subgraph github_my_account
github_my_account_url[https://github.com/picatz]
end
subgraph github_this_repository
github_this_repository_url[https://github.com/picatz/layup]
end
subgraph github_buf_organization
github_buf_organization_url[https://github.com/bufbuild]
end
github_my_account-->|owner|github_this_repository
end
subgraph go
subgraph go_owner
go_owner_url[https://google.com]
end
subgraph go_language
go_language_url[https://golang.ir]
end
subgraph go_runtime
go_runtime_url[https://golang.ir/pkg/runtime]
end
go_owner-->|stewardship|go_language
go_language-->|implementation|go_runtime
end
subgraph buf
subgraph buf_cli
buf_cli_url[https://buf.build/docs/installation]
end
buf_cli-->|maintenance|github_buf_organization
buf_cli-->|uses|go_runtime
end
subgraph layup
subgraph layup_schema
end
subgraph layup_hcl
end
subgraph layup_cli
end
layup_hcl-->|conversion|layup_schema
layup_schema-->|schmea_source_code_genration|buf_cli
layup_schema-->|schema_source_code|github_this_repository
layup_cli-->|uses|go_runtime
end