# Bean & Bark: your model is the schema

In Part 1 you shaped keys by hand — `CUSTOMER#c-014`, `begins_with(sk, 'ORDER#')` — and held the whole design in your head. That works for *anyone* with a DynamoDB table. This is **Part 2**: you're not just querying Bean & Bark anymore, you're building it on the **Kanject platform** — where you write the model once as annotated C#, and the keys, the schema DynoStudio reads, and (next chapter) your data-access code all flow from that one place.

> **The shift — and what you'll need:** Part 1 needed nothing but a DynamoDB table. Part 2 uses **Kanject Core** — the SDK and the `kanject` CLI. If you don't build on Kanject, Part 1 already made you productive; this part is for teams whose app *is* the model.

**You'll learn**

- Scaffold a Kanject service and its **dev / stage / prod** stages with the `kanject` CLI
- Declare keys and a **uniqueness constraint** as attributes on a plain C# class
- See DynoStudio read that *same* model — schema, markers, and access patterns — with nothing defined twice

## One command, one model

`kanject new` scaffolds the service from a template; `kanject init` writes its manifest and one stage file per environment — the same `dev / stage / prod` you learned to respect in Part 1, now created for you:

```bash
# scaffold the service from a template, then wire its stages
kanject new kbs-lambda-netcore-api --name bean-and-bark
kanject init --stages dev,stage,prod --region eu-west-1
```

Now the model. Instead of *remembering* that a customer's key is `CUSTOMER#{id}`, you **declare** it — and you mark the email unique, right on the property:

```csharp
using Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Annotations.Attributes;

[Table("BeanAndBark")]
public class Customer
{
    [KeyTemplate("CUSTOMER#{CustomerId}")]   // the partition key, declared
    public string Pk { get; set; }

    [KeyTemplate("PROFILE")]                 // one profile row per customer
    public string Sk { get; set; }

    public string CustomerId { get; set; }
    public string Name { get; set; }

    [Unique("Email")]                        // one account per email — enforced
    public string Email { get; set; }

    public string Region { get; set; }
}
```

Open this in DynoStudio's **Model Builder** and it parses the very same file. The entity, its key templates, and the marker all appear — because the studio reads exactly the attributes you wrote. There is no second schema to define, and nothing to keep in sync.

> **What `[Unique("Email")]` actually buys you:** One attribute, two guarantees. It **generates a `FindCustomerByEmailAsync(email)`** — the email you want to look up *is* the method's argument, so there is no query to hand-write — and it **enforces one-account-per-email** by writing a reserved row (`$$Unique#Email#…`) into the base table, a constraint DynamoDB has no native concept of. You didn't write the lookup, and you didn't write the guard; the marker is both.

```csharp
// generated from [Unique("Email")] — the email is the argument, no query to write
Customer? ada = await customers.FindCustomerByEmailAsync("ada@beanandbark.co");
```

**Recap**

- On Kanject, **the model is the source**: `kanject init` scaffolds the service and its dev/stage/prod stages, and keys are attributes (`[KeyTemplate]`), not strings you remember.
- DynoStudio reads the *same* annotated C# your app is built from — so its schema, markers, and access patterns are **never defined twice**.
- `[Unique("Email")]` is one attribute that yields **both** a generated `FindCustomerByEmailAsync` and a table-enforced uniqueness constraint (a `$$Unique#…` reserved row).

**Check yourself**

**1. On the Kanject platform, where does DynoStudio get its understanding of your schema?**

- It parses the same annotated C# model your app is built from ✓ — the `[Table]` / `[KeyTemplate]` / marker attributes are the single source of truth — the studio reads them directly, so there's no separate schema to define or drift.
- You redraw the schema in the studio and keep it in sync by hand — that's exactly the drift Kanject removes — the model *is* the schema, read from your code, not re-entered.
- It infers everything by sampling the live table's items — sampling can profile unknown data (Part 1's job), but here the studio reads your declared model — keys and constraints and all — not a guess from the rows.

**2. What does `[Unique("Email")]` on a property give you?**

- Both a generated FindCustomerByEmailAsync and a table-enforced uniqueness guarantee ✓ — the marker is a lookup *and* a constraint — a reserved `$$Unique#Email#…` row makes a duplicate email impossible, and the method is generated for you.
- A new GSI with Email as its partition key — a marker isn't a GSI — it's a reserved row in the base table. It enforces uniqueness, which a GSI cannot do.
- Only compile-time validation; nothing changes in DynamoDB — it very much changes DynamoDB — it writes a reserved row that makes a second account with the same email fail.

**Try it yourself**

**1. Model the product**

Add a `Product` entity to the same `BeanAndBark` table: its partition key is `PRODUCT#{Sku}`, and the SKU must be unique.

_Hint:_ Same shape as `Customer`: a `[Table("BeanAndBark")]` class, a `[KeyTemplate]` on the key property, and `[Unique(...)]` on the SKU.

_Solution:_ A `[Table("BeanAndBark")]` class with `[KeyTemplate("PRODUCT#{Sku}")]` on the key and `[Unique("Sku")]` on the SKU — which generates `FindProductBySkuAsync` and guarantees no two products share a SKU.

```csharp
[Table("BeanAndBark")]
public class Product
{
    [KeyTemplate("PRODUCT#{Sku}")] public string Pk { get; set; }
    [KeyTemplate("PROFILE")]       public string Sk { get; set; }

    [Unique("Sku")] public string Sku { get; set; }
    public string Name { get; set; }
}
```

> **Where next?:** Your model is the studio's schema and the table's rulebook. [Marker indexes](https://www.kanject.com/docs/dynostudio-marker-indexes/) is the full reference for `[Unique]`, `[Collection]`, and `[Range]` and the reserved rows behind them. Next, the model becomes the *code*: you'll generate the data layer and ship a feature — the round-trip that closes the loop.

---
_Source: https://www.kanject.com/docs/dynostudio-bean-bark-model/ · Kanject Docs_
