Tutorial

A step by step tutorial that will teach you the fundamentals of building and maintaining a project with Wrapt.

What are We Building?

Let's say that we want to create an application to manage recipes. We're going to call it CarbonKitchen and it should start out by storing recipes and their associated ingredients.

Creating a Template File

To get things started, we we are going to build a bare bones yaml or json file to lay out what we want our project to look like. This file is going to describe what our particular domain looks like and then we can add additional features and logic afterward.

Don't feel pressured to get this exactly correct from the get go. This file is NOT meant to be a concrete implementation of your API, just a starting point that you can build on over time.

So what's this file look like? Let's go over an example. For a full list of the configuration option details, you can go to the domain-template page.

Basic Domain and Bounded Context Setup

We're going to start out by creating a new yaml file and adding a domain name of CarbonKitchen.

DomainName: CarbonKitchen

Next, we need to lay out each of the logical contextual boundaries within our domain (a.k.a. bounded contexts). In this domain, three come to mind: 1) Recipes to store our recipes 2) Planning to store our meal plans and 3) Shopping to store our shopping lists. Let's start out with the RecipeManagement bounded context and name it with a property called SolutionName.

DomainName: CarbonKitchen
BoundedContexts:
- SolutionName: RecipeManagement

Then, we'll describe our database set up for each bounded context using the DbContext property. I'm also going to set port to run on locally, but this is optional.

DomainName: CarbonKitchen
BoundedContexts:
- SolutionName: RecipeManagement
  Port: 5005
  DbContext:
   ContextName: RecipesDbContext
   DatabaseName: RecipeManagement
   Provider: SqlServer

Entity Setup

Then let's add in our Recipe Entity with a variety of properties based on our requirements. Be sure to check the entity properties page to see what's required and what defaults are used.

DomainName: CarbonKitchen
BoundedContexts:
- SolutionName: RecipeManagement
  Port: 5005
  DbContext:
   ContextName: RecipesDbContext
   DatabaseName: RecipeManagement
   Provider: SqlServer
  Entities:
  - Name: Recipe
    Properties:
    - Name: RecipeId
      IsPrimaryKey: true
      Type: int
      CanFilter: true
      CanSort: true
    - Name: Title
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Directions
      Type: string
      CanFilter: true
      CanSort: true
    - Name: RecipeSourceLink
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Description
      Type: string
      CanFilter: true
      CanSort: true
    - Name: ImageLink
      Type: string
      CanFilter: true
      CanSort: true

Note that Entities is a list, so we can add more than one if we want:

DomainName: CarbonKitchen
BoundedContexts:
- SolutionName: RecipeManagement
  Port: 5005
  DbContext:
   ContextName: RecipesDbContext
   DatabaseName: RecipeManagement
   Provider: SqlServer
  Entities:
  - Name: Recipe
    Properties:
    - Name: RecipeId
      IsPrimaryKey: true
      Type: int
      CanFilter: true
      CanSort: true
    - Name: Title
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Directions
      Type: string
      CanFilter: true
      CanSort: true
    - Name: RecipeSourceLink
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Description
      Type: string
      CanFilter: true
      CanSort: true
    - Name: ImageLink
      Type: string
      CanFilter: true
      CanSort: true
  - Name: Ingredient
    Properties:
    - Name: IngredientId
      IsPrimaryKey: true
      Type: guid
      CanFilter: true
      CanSort: true
    - Name: RecipeId
      Type: int?
      CanFilter: true
      CanSort: true
    - Name: Name
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Unit
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Amount
      Type: double?
      CanFilter: true
      CanSort: true

Environment and Swagger Setup

Now I'm going to add in a few environments as well as some Swagger documentation. Both of these are optional, but good to have. You can also add other optional properties like authorization settings if you'd like to include those here.

DomainName: CarbonKitchen
BoundedContexts:
- SolutionName: RecipeManagement
  Port: 5005
  DbContext:
   ContextName: RecipesDbContext
   DatabaseName: RecipeManagement
   Provider: SqlServer
  Entities:
  - Name: Recipe
    Properties:
    - Name: RecipeId
      IsPrimaryKey: true
      Type: int
      CanFilter: true
      CanSort: true
    - Name: Title
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Directions
      Type: string
      CanFilter: true
      CanSort: true
    - Name: RecipeSourceLink
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Description
      Type: string
      CanFilter: true
      CanSort: true
    - Name: ImageLink
      Type: string
      CanFilter: true
      CanSort: true
  - Name: Ingredient
    Properties:
    - Name: IngredientId
      IsPrimaryKey: true
      Type: guid
      CanFilter: true
      CanSort: true
    - Name: RecipeId
      Type: int?
      CanFilter: true
      CanSort: true
    - Name: Name
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Unit
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Amount
      Type: double?
      CanFilter: true
      CanSort: true
  Environments:
    - EnvironmentName: Production
      ConnectionString: "Data Source=myprodserver;Initial Catalog=CarbonKitchen;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;"
      ProfileName: Prod
    - EnvironmentName: Qa
      ConnectionString: "Data Source=myqaserver;Initial Catalog=CarbonKitchen;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;"
      ProfileName: Qa
  SwaggerConfig:
    Title: Carbon Kitchen Recipes
    Description: Our API uses a REST based design, leverages the JSON data format, and relies upon HTTPS for transport. We respond with meaningful HTTP response codes and if an error occurs, we include error details in the response body. API Documentation is at carbonkitchen.com/dev/docs
    AddSwaggerComments: true
    ApiContact: 
      Name: Carbon Kitchen
      Email: devsupport@CarbonKitchen.com
      Url: https://www.carbonkitchen.com

Creating a New Domain Project

Now that we've put together our domain layout, let's actually build our project. To start, make sure you've followed the install instructions. Then, open Command Prompt, Powershell, Terminal, etc. and cd to whatever directory you want to create your project repository in. Finally, we just need to run one command:

craftsman new:domain C:\Users\Paul\Documents\WraptTemplates\NewCarbonKitchen.yaml

Note that the filepath here should match wherever you saved your yaml file. A relative path should also work.

Once that's done, Craftsman will have added an entire API for you in whatever directory you ran this in. Check out the project architecture and organization page for more details.

Using Your API

Now you can cd into your bounded context directory and run dotnet run --project {SolutionName}.webapi and your API is up and all the boilerplate taken care of 🥳

Adding a New Entity

But hold on, we have this existing project, but what if we wanted to add a new entity to capture shopping list items so that we can add ingredients we need for our grocery outing? All we need to do is create another yaml or json file (based on the add entity template) that describes this new entity. Something like this:

AddSwaggerComments: true
Entities:
- Name: ShoppingListItem
  Properties:
  - Name: ShoppingListItemId
    IsPrimaryKey: true
    Type: guid
    CanFilter: true
    CanSort: true
  - Name: Amount
    Type: double?
    CanFilter: true
    CanSort: true
  - Name: Name
    Type: string
    CanFilter: true
    CanSort: true
  - Name: Category
    Type: string
    CanFilter: true
    CanSort: true
  - Name: Unit
    Type: string
    CanFilter: true
    CanSort: true

Then, we can add that entity to our project using the add:entity command:

craftsman add:entity C:\Users\Paul\Documents\WraptTemplates\ShoppingListEntity.yaml

Adding a New Property

Now what if we wanted to add a new property to determine whether or not that shopping list item has been acquired and put into our cart. There's a command for that too! And we don't even need a file this time!

Let's say we wanted to add a nullable boolean called 'Acquired' that can be filtered and sorted. We could do something like this:

craftsman add:prop --entity ShoppingListItem --name Acquired --type bool? --filter true -sort true

We could also use shorthand and do something like this:

craftsman add:property -e ShoppingListItem -n Acquired -t bool? -f true -s true

Adding a New Bounded Context

So we built out the bounded context for our Recipes, but what if we wanted to add the Shopping bounded context? Let's make a new file (using a bounded-contexts template to capture those details:

BoundedContexts:
- SolutionName: ShoppingManagement
  Port: 5010
  DbContext:
   ContextName: ShoppingDbContext
   DatabaseName: ShoppingManagement
   Provider: Postgres
  Entities:
  - Name: ShoppingList
    Properties:
    - Name: ShoppingListId
      IsPrimaryKey: true
      Type: guid
      CanFilter: true
      CanSort: true
    - Name: Store
      Type: string
      CanFilter: true
      CanSort: true
  Environments:
    - EnvironmentName: Production
      ConnectionString: "User ID=postgres;Password=password;Host=prodserver;Port=5432;Database=Shopping;"
      ProfileName: Prod
    - EnvironmentName: Qa
      ConnectionString: "User ID=postgres;Password=password;Host=qaserver;Port=5432;Database=Shopping;"
      ProfileName: Qa
  SwaggerConfig:
    Title: Carbon Kitchen Shopping
    Description: Our API uses a REST based design, leverages the JSON data format, and relies upon HTTPS for transport. We respond with meaningful HTTP response codes and if an error occurs, we include error details in the response body. API Documentation is at carbonkitchen.com/dev/docs
    AddSwaggerComments: true
    ApiContact: 
      Name: Carbon Kitchen
      Email: devsupport@CarbonKitchen.com
      Url: https://www.carbonkitchen.com

Then we can go to our domain directory (the one with the .gitignore):

cd C:\my\domain\directory

Then we can use the add:bc command to add the new context:

add:bc C:\fullpath\my-new-bc.yaml

Customization

Great! Now we have a new project, but we want to add some custom business logic. Maybe we want to do some special validation when we're adding a new recipe or add another endpoint to manage ingredients in batch. This is all completely doable! For details, see the page on customizing Wrapt projects.