Documentation

tutorials/your-first-component.md

Your First Component

Welcome! This tutorial will guide you through understanding and building your first Dynaplex component. By the end, you'll understand component structure and have created a simple working component.

What You'll Learn

  • Component structure and organization
  • The role of .Abstractions projects
  • Creating interfaces and models
  • Basic implementation patterns
  • How components integrate with Dynaplex

Prerequisites

Understanding Component Anatomy

Every Dynaplex component follows a consistent project structure:

engines/my-component/
├── src/
│   ├── Acsis.Dynaplex.Engines.MyComponent.Abstractions/  # 📋 Contracts
│   │   ├── IMyComponentApi.cs                      # Main interface
│   │   ├── Models/                                 # DTOs and requests
│   │   └── Configuration/                          # Options classes
│   │
│   ├── Acsis.Dynaplex.Engines.MyComponent.Database/      # 🗄️ Database (if needed)
│   │   ├── MyComponentDb.cs                        # DbContext
│   │   ├── Entities/                               # Entity models
│   │   └── Migrations/                             # EF Core migrations
│   │
│   ├── Acsis.Dynaplex.Engines.MyComponent/               # ⚙️ Service implementation
│   │   ├── Program.cs                              # Service entry point
│   │   ├── MyComponentApi.cs                       # API implementation
│   │   └── Services/                               # Business logic
│   │
│   └── Acsis.Dynaplex.Engines.MyComponent.ApiClient/     # 🔗 HTTP client (generated)
│
└── test/                                           # 🧪 Tests
    └── Acsis.Dynaplex.Engines.MyComponent.Tests/

Why This Structure?

.Abstractions: Contains the contract (what the component does)
.Database: Contains the data model (entities and migrations)
Main project: Contains the implementation (how it does it)
.ApiClient: Auto-generated HTTP client for type-safe communication

Note: Database migrations are run by the centralized db-manager component.

This separation ensures clean boundaries and enables microservices architecture.

Your First Component: "Greeter"

Let's build a simple greeting service to learn the pattern.

Step 1: Create the Directory Structure

# From repository root
cd engines
mkdir -p greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/Models
mkdir -p greeter/src/Acsis.Dynaplex.Engines.Greeter
mkdir -p greeter/test/Acsis.Dynaplex.Engines.Greeter.Tests

Step 2: Define the Contract

Create the interface that defines what your component does.

File: engines/greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/IGreeterApi.cs

namespace Acsis.Dynaplex.Engines.Greeter.Abstractions;

/// <summary>
/// API for greeting users
/// </summary>
public interface IGreeterApi
{
    /// <summary>
    /// Gets a personalized greeting
    /// </summary>
    /// <param name="name">The person's name</param>
    /// <returns>A greeting message</returns>
    Task<string> GetGreetingAsync(string name);

    /// <summary>
    /// Gets greeting statistics
    /// </summary>
    /// <returns>Statistics about greetings sent</returns>
    Task<GreetingStats> GetStatsAsync();
}

Step 3: Create a Model

Models live in .Abstractions because they're part of the contract.

File: engines/greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/Models/GreetingStats.cs

namespace Acsis.Dynaplex.Engines.Greeter.Abstractions.Models;

/// <summary>
/// Statistics about greetings
/// </summary>
public class GreetingStats
{
    /// <summary>
    /// Total greetings sent
    /// </summary>
    public int TotalGreetings { get; set; }

    /// <summary>
    /// Most common name greeted
    /// </summary>
    public string? MostPopularName { get; set; }
}

Step 4: Create the .Abstractions Project

File: engines/greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/Acsis.Dynaplex.Engines.Greeter.Abstractions.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
</Project>

Step 5: Implement the API

Now implement the interface in the main project.

File: engines/greeter/src/Acsis.Dynaplex.Engines.Greeter/GreeterApi.cs

using Acsis.Dynaplex.Engines.Greeter.Abstractions;
using Acsis.Dynaplex.Engines.Greeter.Abstractions.Models;
using Microsoft.Extensions.Logging;

namespace Acsis.Dynaplex.Engines.Greeter;

public class GreeterApi : IGreeterApi
{
    private readonly ILogger<GreeterApi> _logger;
    private static int _greetingCount = 0;

    public GreeterApi(ILogger<GreeterApi> logger)
    {
        _logger = logger;
    }

    public Task<string> GetGreetingAsync(string name)
    {
        _greetingCount++;
        _logger.LogInformation("Greeting {Name} (total: {Count})", name, _greetingCount);
        return Task.FromResult($"Hello, {name}! Welcome to Dynaplex.");
    }

    public Task<GreetingStats> GetStatsAsync()
    {
        return Task.FromResult(new GreetingStats
        {
            TotalGreetings = _greetingCount,
            MostPopularName = "World" // Simplified for tutorial
        });
    }
}

Step 6: Create the Implementation Project

File: engines/greeter/src/Acsis.Dynaplex.Engines.Greeter/Acsis.Dynaplex.Engines.Greeter.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="$(GreeterAbstractions)" />
    </ItemGroup>
</Project>

Step 7: Register Your Component

Add to Directory.Build.props:

<PropertyGroup>
    <GreeterAbstractions>$(EngineDir)greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/Acsis.Dynaplex.Engines.Greeter.Abstractions.csproj</GreeterAbstractions>
</PropertyGroup>

Step 8: Build and Test

# Build your component
dotnet build engines/greeter/src/Acsis.Dynaplex.Engines.Greeter.Abstractions/
dotnet build engines/greeter/src/Acsis.Dynaplex.Engines.Greeter/

# If successful, your component is ready!

What You've Learned

Component structure - Four projects with clear purposes
Contracts - Interfaces define what components do
Models - Live in .Abstractions as part of the contract
Implementation - Implements the interface
Dependency injection - Constructor injection for logging

Next Steps

Now that you understand the basics:

  1. Create a complete component - Full step-by-step guide with ASP.NET Core service
  2. Learn component patterns - Best practices and advanced patterns
  3. Explore existing components - See real-world examples

Common Questions

Q: Why separate .Abstractions from implementation?
A: This enables microservices architecture where services communicate via HTTP. Each service only knows about other services' contracts, not their implementation.

Q: When do I use .ApiClient?
A: When your component needs to call another component's HTTP API. The client is auto-generated from the OpenAPI specification.

Q: Do all components need a database?
A: No! Only create a .Database project if your component stores data. If you do, register it with the db-manager.

Q: Can components reference each other?
A: Services can only reference other services' .Abstractions projects. At runtime, they communicate via HTTP using generated API clients.


Great job! You've built your first Dynaplex component. 🎉