Documentation
how-to/modify-component.md
How to Modify Components
This guide shows you how to add features, modify APIs, and extend existing Dynaplex components.
Adding New Methods to an API
When you need to add functionality to an existing component:
Step 1: Add to the Interface
Update the interface in the .Abstractions project:
// In Acsis.Dynaplex.Engines.MyFeature.Abstractions/IMyFeatureApi.cs
public interface IMyFeatureApi
{
// Existing methods...
Task<FeatureItemModel?> GetByIdAsync(int id);
// NEW: Add your new method
/// <summary>
/// Searches for feature items by name
/// </summary>
/// <param name="searchTerm">The search term</param>
/// <returns>Matching feature items</returns>
Task<List<FeatureItemModel>> SearchByNameAsync(string searchTerm);
}
Step 2: Implement in the Service
Add the implementation in the main project:
// In Acsis.Dynaplex.Engines.MyFeature/MyFeatureApi.cs
public class MyFeatureApi : IMyFeatureApi
{
// Existing implementation...
// NEW: Implement the new method
public async Task<List<FeatureItemModel>> SearchByNameAsync(string searchTerm)
{
_logger.LogInformation("Searching for items with term: {SearchTerm}", searchTerm);
// Your implementation here
var results = await _repository.SearchAsync(searchTerm);
return results;
}
}
Step 3: Add Endpoint (if needed)
Update Program.cs to expose the new functionality:
// In Program.cs
app.MapGet("/api/features/search", async (
[FromQuery] string q,
IMyFeatureApi api) =>
{
var results = await api.SearchByNameAsync(q);
return Results.Ok(results);
});
Step 4: Build and Test
dotnet build engines/my-feature/
dotnet test engines/my-feature/test/
Adding New Models
Models represent data structures used by your API.
Step 1: Create the Model in .Abstractions
// In Acsis.Dynaplex.Engines.MyFeature.Abstractions/Models/FeatureItemSummary.cs
namespace Acsis.Dynaplex.Engines.MyFeature.Abstractions.Models;
/// <summary>
/// Lightweight summary of a feature item
/// </summary>
public class FeatureItemSummary
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public int ItemCount { get; set; }
}
Step 2: Use in Your API
public interface IMyFeatureApi
{
/// <summary>
/// Gets a summary of all feature items
/// </summary>
Task<List<FeatureItemSummary>> GetSummariesAsync();
}
Best Practices for Models
✅ Use data annotations for validation
✅ Document with XML comments
✅ Make classes sealed unless inheritance is needed
✅ Use nullable reference types appropriately
✅ Consider backwards compatibility when modifying existing models
Adding Dependencies
External NuGet Packages
Add to the appropriate .csproj file:
<!-- In Acsis.Dynaplex.Engines.MyFeature.csproj -->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
Referencing Other Components
⚠️ IMPORTANT: Only reference .Abstractions projects of other components.
<!-- CORRECT: Reference the .Abstractions project -->
<ItemGroup>
<ProjectReference Include="$(EventsAbstractions)" />
</ItemGroup>
<!-- WRONG: Never reference implementation projects -->
<ItemGroup>
<ProjectReference Include="$(Events)" /> <!-- ❌ This will fail -->
</ItemGroup>
At runtime, services communicate via HTTP using auto-generated API clients.
Using Another Component's API
public class MyFeatureApi : IMyFeatureApi
{
private readonly IEventsApiClient _events;
public MyFeatureApi(IEventsApiClient events)
{
_events = events;
}
public async Task DoSomething()
{
// Call another component via HTTP client
await _events.PublishEventAsync(new Event { /* ... */ });
}
}
Testing Components
Unit Test Structure
test/Acsis.Dynaplex.Engines.MyFeature.Tests/
├── Acsis.Dynaplex.Engines.MyFeature.Tests.csproj
├── MyFeatureApiTests.cs
├── Models/
│ └── FeatureItemModelTests.cs
├── TestHelpers/
│ └── MyFeatureTestHelpers.cs
└── TestData/
└── SampleFeatureItems.json
Example Unit Test
using Acsis.Dynaplex.Engines.MyFeature.Abstractions;
using Acsis.Dynaplex.Engines.MyFeature.Abstractions.Models;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Acsis.Dynaplex.Engines.MyFeature.Tests;
public class MyFeatureApiTests
{
private readonly IMyFeatureApi _api;
public MyFeatureApiTests()
{
var logger = NullLogger<MyFeatureApi>.Instance;
_api = new MyFeatureApi(logger);
}
[Fact]
public async Task GetByIdAsync_WithValidId_ReturnsFeatureItem()
{
// Arrange
const int id = 123;
// Act
var result = await _api.GetByIdAsync(id);
// Assert
Assert.NotNull(result);
Assert.Equal(id, result.Id);
Assert.NotEmpty(result.Name);
}
[Fact]
public async Task CreateAsync_WithValidRequest_ReturnsCreatedItem()
{
// Arrange
var request = new CreateFeatureItemRequest
{
Name = "Test Feature",
Description = "Test Description"
};
// Act
var result = await _api.CreateAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal(request.Name, result.Name);
Assert.Equal(request.Description, result.Description);
Assert.True(result.Id > 0);
}
[Fact]
public async Task CreateAsync_WithNullRequest_ThrowsArgumentNullException()
{
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() =>
_api.CreateAsync(null!));
}
}
Integration Tests
Test service-to-service communication:
public class MyFeatureIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public MyFeatureIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetFeature_ReturnsExpectedResponse()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/features/1");
// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Feature Item", content);
}
}
Common Modification Scenarios
Adding Configuration
Step 1: Create options class in .Abstractions:
// Acsis.Dynaplex.Engines.MyFeature.Abstractions/Configuration/MyFeatureOptions.cs
public class MyFeatureOptions
{
public string ApiKey { get; set; } = string.Empty;
public int MaxRetries { get; set; } = 3;
public bool EnableCaching { get; set; } = true;
}
Step 2: Configure in appsettings.json:
{
"MyFeature": {
"ApiKey": "your-api-key",
"MaxRetries": 5,
"EnableCaching": true
}
}
Step 3: Use in implementation:
public class MyFeatureApi : IMyFeatureApi
{
private readonly MyFeatureOptions _options;
public MyFeatureApi(IOptions<MyFeatureOptions> options)
{
_options = options.Value;
}
}
Step 4: Register in Program.cs:
builder.Services.Configure<MyFeatureOptions>(
builder.Configuration.GetSection("MyFeature"));
Adding Background Services
// In Acsis.Dynaplex.Engines.MyFeature/Services/MyBackgroundService.cs
public class MyBackgroundService : BackgroundService
{
private readonly ILogger<MyBackgroundService> _logger;
public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Background service running at: {Time}", DateTimeOffset.Now);
// Do work here
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
Register in Program.cs:
builder.Services.AddHostedService<MyBackgroundService>();
Next Steps
- Component patterns reference - Best practices
- Testing guide - Comprehensive testing
- Troubleshooting - Common issues
Checklist
Before committing your changes:
- Interface changes are in
.Abstractionsproject - Implementation is complete and tested
- Unit tests added for new functionality
- XML documentation comments added
- Configuration properly registered
- Solution builds without warnings
- Existing tests still pass
- OpenAPI documentation updated (if endpoints changed)