Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App structure: DNAs page #516

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f154488
lifecycle callbacks and zome functions pages
pdaoust Jan 15, 2025
8fc90cd
add new pages to navs
pdaoust Jan 15, 2025
ae585c0
link up all references to two new pages
pdaoust Jan 15, 2025
e20390e
remove redundant descriptions of lifecycles, add examples for relaxed
pdaoust Jan 15, 2025
f16a4a5
add under-the-hood for CreateLink and DeleteLink
pdaoust Jan 15, 2025
cd3eee3
add snapshotted to dict
pdaoust Jan 15, 2025
2ccb02d
fix broken JSON
pdaoust Jan 15, 2025
5cb51e1
fix broken links
pdaoust Jan 15, 2025
60cf346
fix hdk_entry_defs, whoops
pdaoust Jan 15, 2025
060a7d8
add example for post_commit
pdaoust Jan 15, 2025
1010a44
simplify/elaborate post_commit example
pdaoust Jan 16, 2025
39813c0
Merge branch 'feat/guide/app-structure-zomes' into feat/guide/app-str…
pdaoust Jan 17, 2025
5103988
improve language around callbacks and lifecycle hooks, plus a couple …
pdaoust Jan 17, 2025
0cc2fca
little bit more of the same
pdaoust Jan 17, 2025
a1c3af3
test/fix all code samples in callbacks page
pdaoust Jan 20, 2025
26b63dd
small edits to callbacks page
pdaoust Jan 20, 2025
e2c3f7b
link from identifiers to post_commit page (plus a typo fix)
pdaoust Jan 20, 2025
2a74d82
reference/further reading for zomes and callbacks pages
pdaoust Jan 20, 2025
f7d3638
Merge branch 'feat/guide/app-structure-zome-functions' into feat/guid…
pdaoust Jan 21, 2025
05fbd84
DNAs page
pdaoust Jan 21, 2025
445e799
add DNAs page to nav and other references
pdaoust Jan 21, 2025
f618460
mention using a Cargo workspace
pdaoust Jan 21, 2025
994c997
fix broken URLs and a typo
pdaoust Jan 21, 2025
854c45b
add section to DNAs on remote calling
pdaoust Jan 22, 2025
7239d69
add DNAs and hApps to build guide overview
pdaoust Jan 22, 2025
6de7e31
typo in remote/bridge call table
pdaoust Jan 22, 2025
ae3614a
fix lost mention of integrity modifiers
pdaoust Jan 22, 2025
f0556bb
replace hc dna init with hc scaffold dna
pdaoust Jan 27, 2025
86fcefb
change DNA bundling to use npm
pdaoust Jan 27, 2025
a111ffb
Apply suggestions from code review
pdaoust Jan 27, 2025
6763280
remove mention of Moss from DNAs
pdaoust Jan 27, 2025
012eb5f
Merge branch 'feat/guide/app-structure-dnas' of github.com:holochain/…
pdaoust Jan 27, 2025
7b7e133
clarify wording about throwaway DHTs
pdaoust Jan 27, 2025
84da374
update language about coordinator/integrity deps w/ bug warning
pdaoust Jan 29, 2025
7a71396
tiny text edit
pdaoust Jan 29, 2025
2646276
Apply suggestions from code review
pdaoust Jan 30, 2025
4557f70
Merge branch 'main' into feat/guide/app-structure-dnas
pdaoust Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell/words-that-should-exist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ified
interoperating
lifecycle
lifecycles
passcode
permissioned
permissivity
redistributable
Expand All @@ -25,6 +26,7 @@ runtimes
sandboxed
sandboxing
scaffolder
snapshotted
spacebar
todo
todos
Expand Down
6 changes: 5 additions & 1 deletion src/pages/_data/navigation/mainNav.json5
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
},
{ title: "Build", url: "/build/", children: [
{ title: "Application Structure", url: "/build/application-structure/", children: [
{ title: "Zomes", url: "/build/zomes/" },
{ title: "Zomes", url: "/build/zomes/", children: [
{ title: "Lifecycle Events and Callbacks", url: "/build/callbacks-and-lifecycle-hooks/" },
{ title: "Zome Functions", url: "/build/zome-functions/" },
] },
{ title: "DNAs", url: "/build/dnas/" },
]},
{ title: "Working with Data", url: "/build/working-with-data/", children: [
{ title: "Identifiers", url: "/build/identifiers/" },
Expand Down
6 changes: 5 additions & 1 deletion src/pages/build/application-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ title: Application Structure

* Application Structure (this page)
* [Zomes](/build/zomes/) --- integrity vs coordinator, how to structure and compile
* DNAs (coming soon) --- what they're used for, how to specify and bundle
* [Lifecycle Events and Callbacks](/build/callbacks-and-lifecycle-hooks/) --- writing functions that respond to events in a hApp's lifecycle
* [Zome Functions](/build/zome-functions/) --- writing your hApp's back-end API
* [DNAs](/build/dnas/) --- what they're used for, how to specify and bundle
* hApps (coming soon) --- headless vs UI-based, how to bundle and distribute
:::

Expand Down Expand Up @@ -61,6 +63,8 @@ This means you can hot-swap coordinators as you fix bugs and add features, witho

Because each DNA has its own separate peer network and data store, you can use the DNA concept to come up with creative approaches to [privacy](https://dialnet.unirioja.es/servlet/articulo?codigo=8036267) and access, separation of responsibilities, or data retention.

[Read more on the DNAs page](/build/dnas/).

### hApp

One or more DNAs come together in a **hApp** (Holochain app). Each DNA fills a named **role** in the hApp, and you can think of it like a [microservice](https://en.wikipedia.org/wiki/Microservices).
Expand Down
340 changes: 340 additions & 0 deletions src/pages/build/callbacks-and-lifecycle-hooks.md
pdaoust marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

220 changes: 220 additions & 0 deletions src/pages/build/dnas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
---
title: "DNAs"
---

::: intro
A **DNA** is a bundle of one or more [**zomes**](/build/zomes/), along with optional **DNA modifiers**. Together, the zomes and DNA modifiers define the executable code and settings for a single **peer-to-peer network**.
:::

## DNAs: the 'rules of the game' for a network

Holochain supports multiple, separate peer-to-peer networks, each with its own membership and shared [graph database](/build/working-with-data/). Each network is backed by its own DNA, whose executable code and settings create the 'rules of the game' for the network.

The **DNA hash** is the unique identifier for a network. The **DNA integrity modifiers** contribute to this hash; the rest of the DNA does not. That means that any change to integrity modifiers will change the DNA hash and form a new network if agents install and run it.

The contents of a DNA are specified with a **manifest file**.

## Create a DNA

If you use the [scaffolding tool](/get-started/3-forum-app-tutorial/), it'll scaffold a working directory for every DNA you scaffold.

You can also use the `hc` command in the [Holonix dev shell](/get-started/#2-installing-holochain-development-environment) to create a bare working directory:

```bash
hc dna init movies
```

You'll be prompted to enter a name and [**network seed**](#network-seed). After that it'll create a folder called `movies` that contains a basic `dna.yaml` file with your responses to the prompts.
jost-s marked this conversation as resolved.
Show resolved Hide resolved

## Specify a DNA manifest

A DNA manifest is written in [YAML](https://yaml.org/). It contains metadata about the DNA, a section for **integrity modifiers**, and a list of coordinator zomes for interacting with the DNA.

If you want to write your own manifest file, name it `dna.yaml` and give it the following structure. This example assumes that all of your zomes are in a folder called `zomes/`. Afterwards we'll explain what the fields mean.

```yaml
manifest_version: '1'
name: movies
integrity:
network_seed: null
properties:
foo: bar
baz: 123
origin_time: 1735841273312901
zomes:
- name: movies_integrity
hash: null
bundled: 'zomes/movies_integrity/target/wasm32-unknown-unknown/release/movies_integrity.wasm'
coordinator:
zomes:
- name: movies
hash: null
bundled: 'zomes/movies/target/wasm32-unknown-unknown/release/movies.wasm'
dependencies:
- name: movies_integrity
```

### DNA manifest structure at a glance

* `name`: A string for humans to read. This might get used in the admin panel of Holochain [conductors](/concepts/2_application_architecture/#conductor) like [Holochain Launcher](https://github.com/holochain/launcher) or [Moss](https://theweave.social/moss/).
jost-s marked this conversation as resolved.
Show resolved Hide resolved
* `integrity`: Contains all the integrity modifiers for the DNA, the things that **change the DNA hash**.
* `network_seed`: A string that serves only to change the DNA hash without affecting behavior. It acts like a network-wide passcode. {#network-seed}
* `properties`: Arbitrary, application-specific constants. The integrity code can access this, deserialize it, and change its runtime behavior. Think of it as configuration for the DNA.
pdaoust marked this conversation as resolved.
Show resolved Hide resolved
* `origin_time`: The earliest possible timestamp for any data; serves as a basis for coordinating network communication. Pick a date that's guaranteed to be slightly earlier than you expect that the app will start to get used. The scaffolding tool and `hc dna init` will both pick the date you created the DNA.
* `zomes`: A list of all the integrity zomes in the DNA.
* `name`: A unique name for the zome, to be used for dependencies.
* `hash`: Optional. If the hash of the zome at the specified location doesn't match this value, installation will fail.
* Location: The place to find the zome's WebAssembly bytecode. The three options are:
* `bundled`: Expect the file to be part of this [bundle](#bundle-a-dna). The value is a path relative to the manifest file.
* `path`: Get the file from the local filesystem. The value is a filesystem path.
* `url`: Get the file from the web. The value is a URL, of course.
* `coordinator`: Contains all the coordinator bits for the DNA, which do not change the DNA hash and can be modified after the DNA is installed and being used in a [cell](/concepts/2_application_architecture/#cell).
* `zomes`: Currently the only field in `coordinator`. A list of coordinator zomes. Each item in the list is the same as in `integrity.zomes` above, except that the following field is added:
* `dependencies`: The integrity zomes that this coordinator zome depends on. Note that you can leave this field out if there's only one integrity zome (it'll be automatically treated as a dependency). For each dependency in the list, there's one field:
* `name`: A string matching the `name` field of the integrity zome the coordinator zome depends on.

## Bundle a DNA

To roll a DNA manifest and all its zomes into a **DNA bundle**, use the `hc` command on a folder that contains a `dna.yaml` file:

```bash
hc dna pack my_dna/
```

This will create a file in the same folder as the `dna.yaml`, called `<name>.dna`, where `<name>` comes from the `name` field at the top of the manifest.

## Make a coordinator zome depend on an integrity zome

<!-- TODO: write about depending on multiple integrity zomes with `hdk_to_coordinates` -->

In order for a coordinator zome to read and write the entry and link types defined by an integrity zome, you'll need to specify the dependency in a few places.

1. In your coordinator zome's `Cargo.toml` file, specify a dependency on the integrity zome's crate just like you would any Cargo dependency. You can see how to do this in the [Create a coordinator zome section](/build/zomes/#create-a-coordinator-zome) on the Zomes page.
2. In your DNA manifest file, specify the dependency in the `coordinator` section by referencing the integrity zome's `name` field. You can see an example [above](#specify-a-dna-manifest), where the `movies` zome depends on the `movies_integrity` zome. (Remember that you don't need to do this if there's only one integrity zome.)
pdaoust marked this conversation as resolved.
Show resolved Hide resolved

Then, in your coordinator zome's code, import the integrity zome's entry and link types enums and entry structs/enums as needed:

```rust
use movies_integrity::{EntryTypes, LinkTypes, Movie, Director};
```

!!! info Why do I need to specify the dependency twice?
pdaoust marked this conversation as resolved.
Show resolved Hide resolved

It's probably clear to you why you'd need to specify an integrity zome as a Cargo dependency. But why would you need to duplicate that relationship in your DNA manifest?

When you write an entry, its type is stored in the [entry creation action](/build/entries/#entries-and-actions) as a tuple of `(integrity_zome_index, entry_type_index)`, which are just numbers rather than human-readable identifiers. The integrity zomes are indexed by the order they appear in the manifest file, and an integrity zome's entry types are indexed by the order they appear in [an enum with the `#[hdk_entry_types]` macro](/build/entries/#define-an-entry-type).

When your coordinator zome depends on an integrity zome, it doesn't know what that zome's index in the DNA is, so it addresses the zome by its own internal zero-based indexing. Holochain needs to map this to the proper zome index, so it expects your DNA manifest file to tell it about the integrity zome it depends on.

<!-- TODO: ditto re: writing about that nested enum thing -->

!!!

## Single vs multiple DNAs

When do you decide whether a hApp should have more than one DNA? Whenever it makes sense to have multiple separate networks or databases within the hApp. These are the most common use cases:

* **Dividing responsibilities.** For instance, a video sharing hApp may have one group of peers who are willing to index video metadata and offer search services and another group of peers who are willing to host and serve videos, along with people who just want to watch them. This DNA could have `search` and `storage` DNAs, along with a main DNA that allows video watchers to look up peers that are offering services and query them.
* **Creating privileged spaces.** A chat hApp may have both public and private rooms, all [cloned](/resources/glossary/#cloning) from a single `chat_room` DNA. This is a special case, as they all use just one base DNA, but they change just one [integrity modifier](#dna-manifest-structure-at-a-glance) such as the network seed to create new DNAs.
* **Discarding or archiving data.** Because no data is ever deleted in a cell or the network it belongs to, a lot of old data can accumulate. Creating clones of a single storage-heavy DNA, bounded by topic or time period, allows agents to participate in only the networks that contain the information they need. As agents leave networks, unwanted data naturally disappears.
jost-s marked this conversation as resolved.
Show resolved Hide resolved

### Call from one cell to another

Agents can make **remote calls** within a single DNA's network with the [`call_remote`](https://docs.rs/hdk/latest/hdk/p2p/fn.call_remote.html) host function, and they can make **bridge calls** to other cells in the same hApp instance on their own device with the [`call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) host function.

Here's an example using both of these functions to implement the dividing-responsibilities pattern described above. It assumes a hApp with two DNAs -- a main one and another one called `search`, which people enable if they want to become a search provider. We won't show the `search` DNA's code here; just imagine it has a coordinator zome called `search` with a function called `do_search_query`.

```rust
use hdk::prelude::*;

#[derive(Deserialize, Serialize, Debug)]
pub struct SearchQuery {
pub terms: String,
pub keywords: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct SearchInput {
pub query: SearchQuery,
// An agent must ask a specific peer for search results.
// A full app would also contain code for finding out what agents are
// offering search services.
pub peer: AgentPubKey,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct SearchResult {
pub title: String,
pub description: String,
pub video_hash: EntryHash,
}

// Video watcher agents use this function to query a search service provider
// agent.
#[hdk_extern]
pub fn search(input: SearchInput) -> ExternResult<Vec<SearchResult>> {
let response = call_remote(
input.peer,
// The function is in the same zome.
zome_info()?.name,
"handle_search_query".into(),
// No capability secret is required to call this function.
// This assumes that, somewhere else in the code, there's a way for
// agents who want to become search providers to assign an
// unrestricted capability grant to the `handle_search_query`
// function.
None,
input.query,
)?;
match response {
ZomeCallResponse::Ok(data) => data
.decode()
.map_err(|e| wasm_error!("Couldn't deserialize response into search results: {}", e)),
ZomeCallResponse::Unauthorized(_, _, _, _, agent) => Err(wasm_error!("The remote peer {} rejected your search query", agent)),
ZomeCallResponse::NetworkError(message) => Err(wasm_error!(message)),
_ => Err(wasm_error!("An unknown error just happened"))
}
}

// Search provider agents use this function to access their `search` cell,
// which is responsible for indexing and querying video metadata.
#[hdk_extern]
pub fn handle_search_query(query: SearchQuery) -> ExternResult<Vec<SearchResult>> {
let response = call(
CallTargetCell::OtherRole("search".into()),
"search",
"do_search_query".into(),
// Agents don't need a cap secret to call other cells in their own
// hApp instance.
None,
query,
)?;
match response {
ZomeCallResponse::Ok(data) => data
.decode()
.map_err(|e| wasm_error!("Couldn't deserialize response into search results: {}", e)),
_ => Err(wasm_error!("An unknown error just happened"))
}
}
```

Note that **bridging between different cells only happens within one agent's hApp instance**, and **remote calls only happens between two agents in one DNA's network**. For two agents, Alice and Bob, Alice can do this:
pdaoust marked this conversation as resolved.
Show resolved Hide resolved

| ↓ wants to call → | Alice `main` | Alice `search` | Bob `main` | Bob `search` |
| ----------------- | :-----------: | :------------: | :-----------: | :-----------: |
| Alice `main` | `call` | `call` | `call_remote` | ⛔ |
| Alice `search` | `call` | `call` | ⛔ | `call_remote` |

## Next steps

Now that you've created a bare DNA, it's time to [fill it with zomes](/build/zomes/), [define some data types](/build/working-with-data), and write some [callbacks](/build/callbacks-and-lifecycle-hooks/) and an [API](/build/zome-functions/).

## Reference

* [`holochain_types::dna::DnaManifestCurrent`](https://docs.rs/holochain_types/latest/holochain_types/dna/struct.DnaManifestCurrent.html), the underlying type that the DNA manifest gets parsed into. It has a lot of good documentation on the manifest format.

## Further reading

* [Get Started: Installing Holochain Development Environment](/get-started/#2-installing-holochain-development-environment)
* [Core Concepts: Application Architecture](/concepts/2_application_architecture/)
* [Build Guide: Zomes](/build/zomes/)
Loading
Loading