Documentation

how-to/create-library.md

How to Create a Library

This guide provides step-by-step instructions for creating a Dynaplex library project—a pure, stateless code package that can be directly referenced by components.

Prerequisites

When to Create a Library

Before creating a library, verify your code meets ALL qualification criteria:

Criterion Your Code Must...
No persistence Have no DbContext, no migrations
No endpoints Have no Program.cs, no HTTP endpoints
Pure logic Be deterministic, side-effect-free
Cross-cutting Be used by ≥2 components
Stable semantics Change rarely
No business workflows Not implement domain policies

If any criterion is not met, your code belongs in a component, not a library.

See ADR-046: Library Projects for complete criteria.

Step 1: Create Directory Structure

# From repository root
LIBRARY_NAME="my-utility"  # Change to your library name (kebab-case)
LIBRARY_PASCAL="MyUtility"  # PascalCase version

mkdir -p libraries/${LIBRARY_NAME}/src/Acsis.Dynaplex.Strata.${LIBRARY_PASCAL}

Example structure:

libraries/
└── my-utility/
    └── src/
        └── Acsis.Dynaplex.Strata.MyUtility/
            ├── Acsis.Dynaplex.Strata.MyUtility.csproj
            └── [your code files]

Step 2: Create Project File

File: libraries/my-utility/src/Acsis.Dynaplex.Strata.MyUtility/Acsis.Dynaplex.Strata.MyUtility.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsLibrary>true</IsLibrary>
  </PropertyGroup>

  <!--
    IMPORTANT: Libraries must have MINIMAL dependencies.
    Only add packages that are truly necessary.

    FORBIDDEN packages (will cause analyzer errors):
    - Microsoft.EntityFrameworkCore.*
    - Microsoft.AspNetCore.*
  -->
  <ItemGroup>
    <!-- Example: Logging abstractions (if needed) -->
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
  </ItemGroup>

  <!--
    Libraries can reference other libraries.
    Libraries CANNOT reference:
    - Acsis.Dynaplex.Engines.* (any component project)
    - *.Database projects
    - *.ApiClient projects
  -->
  <ItemGroup>
    <!-- Example: Reference another library if needed -->
    <!-- <ProjectReference Include="..\..\..\..\libraries\other-lib\src\Acsis.Dynaplex.Strata.OtherLib\Acsis.Dynaplex.Strata.OtherLib.csproj" /> -->
  </ItemGroup>

</Project>

Step 3: Create Your Code

Libraries should follow these guidelines:

Use Static or Sealed Classes

namespace Acsis.Dynaplex.Strata.MyUtility;

/// <summary>
/// Provides utility methods for [describe purpose].
/// </summary>
public static class MyUtilityHelper {

    /// <summary>
    /// [Describe what this method does].
    /// </summary>
    /// <param name="input">The input to process.</param>
    /// <returns>The processed result.</returns>
    public static string Process(string input) {
        ArgumentNullException.ThrowIfNull(input);
        return input.ToUpperInvariant();
    }
}

Use Immutable Types

namespace Acsis.Dynaplex.Strata.MyUtility;

/// <summary>
/// Represents a [describe the value object].
/// </summary>
public readonly record struct MyValueObject {
    public required string Value { get; init; }

    public static MyValueObject Create(string value) {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);
        return new MyValueObject { Value = value };
    }
}

Guidelines

  • ✅ Use static classes for utility methods
  • ✅ Use sealed classes unless inheritance is needed
  • ✅ Use readonly and immutability where possible
  • ✅ Add comprehensive XML documentation
  • ✅ Validate inputs at public API boundaries
  • ❌ Avoid mutable state
  • ❌ Avoid I/O operations (file, network, database)
  • ❌ Avoid environment/configuration access

Step 4: Add to Solution

dotnet sln acsis-core.slnx add \
  libraries/my-utility/src/Acsis.Dynaplex.Strata.MyUtility/Acsis.Dynaplex.Strata.MyUtility.csproj

Step 5: Create Unit Tests

Create a test project for your library:

mkdir -p libraries/my-utility/test/Acsis.Dynaplex.Strata.MyUtility.Tests

File: libraries/my-utility/test/Acsis.Dynaplex.Strata.MyUtility.Tests/Acsis.Dynaplex.Strata.MyUtility.Tests.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" />
    <PackageReference Include="xunit" />
    <PackageReference Include="xunit.runner.visualstudio" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\src\Acsis.Dynaplex.Strata.MyUtility\Acsis.Dynaplex.Strata.MyUtility.csproj" />
  </ItemGroup>

</Project>

File: libraries/my-utility/test/Acsis.Dynaplex.Strata.MyUtility.Tests/MyUtilityHelperTests.cs

namespace Acsis.Dynaplex.Strata.MyUtility.Tests;

public class MyUtilityHelperTests {

    [Fact]
    public void Process_WithValidInput_ReturnsUppercase() {
        // Arrange
        var input = "hello";

        // Act
        var result = MyUtilityHelper.Process(input);

        // Assert
        Assert.Equal("HELLO", result);
    }

    [Fact]
    public void Process_WithNullInput_ThrowsArgumentNullException() {
        // Act & Assert
        Assert.Throws<ArgumentNullException>(() => MyUtilityHelper.Process(null!));
    }
}

Add tests to solution:

dotnet sln acsis-core.slnx add \
  libraries/my-utility/test/Acsis.Dynaplex.Strata.MyUtility.Tests/Acsis.Dynaplex.Strata.MyUtility.Tests.csproj

Step 6: Verify Build

# Build the library
dotnet build libraries/my-utility/src/Acsis.Dynaplex.Strata.MyUtility/

# Run tests
dotnet test libraries/my-utility/test/Acsis.Dynaplex.Strata.MyUtility.Tests/

Step 7: Use from Components

Components can now reference your library:

<!-- In a component's .csproj file -->
<ItemGroup>
  <ProjectReference Include="..\..\..\..\libraries\my-utility\src\Acsis.Dynaplex.Strata.MyUtility\Acsis.Dynaplex.Strata.MyUtility.csproj" />
</ItemGroup>

Analyzer Enforcement

Libraries are automatically validated by Roslyn analyzers. If you violate library constraints, you'll see compile-time errors:

Rule Trigger Solution
ACSIS0080 Referencing component project Move code to component, not library
ACSIS0081 Using Entity Framework Core Move to component's .Database project
ACSIS0082 Using ASP.NET Core Move to component service project
ACSIS0083 Using HttpClient Move to component service project
ACSIS0084 Referencing .Database project Remove dependency or move to component
ACSIS0085 Referencing .ApiClient project Remove dependency or move to component

Library Checklist

Before considering your library complete:

Structure

  • Located in libraries/{name}/src/Acsis.Dynaplex.Strata.{Name}/
  • Uses Acsis.Dynaplex.Strata.* namespace
  • Added to development solution

Code Quality

  • All classes are static or sealed where appropriate
  • Uses immutability (readonly, init, records)
  • No mutable global state
  • No I/O operations

Dependencies

  • No EF Core packages
  • No ASP.NET Core packages
  • No component project references
  • Minimal external dependencies

Documentation

  • XML documentation on all public APIs
  • README.md in library folder (optional)

Testing

  • Unit tests for all public methods
  • Tests pass: dotnet test

Governance

  • Architecture team approved
  • Added to ADR-046 appendix (if significant)

Existing Libraries

Current libraries in the codebase:

Library Purpose
Acsis.Dynaplex Core infrastructure utilities
Acsis.Dynaplex.Strata.ServiceDefaults Aspire service configuration
Acsis.Dynaplex.Strata.Orchestration Component registration helpers
Acsis.EntLibCompatShim Enterprise Library compatibility
Acsis.Encoding.Gs1 GS1 barcode encoding

Note: These are legacy libraries that will eventually migrate to Acsis.Dynaplex.Strata.* naming.

References