Learnings from the Community Toolkit
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
It's been a few months since we started building the [.NET Aspire Community Toolkit](https://github.com/CommunityToolkit/Aspire) and I thought I'd share some things that we've learnt trying to build integrations outside of the Aspire repo. When it came to building integrations, I wanted to have them as close to the same level of quality that can be found in the Aspire repo, across API design, documentation, and testing, while also behaving as close as we could make them to integrations from the Aspire repo (I'll refer to these as _native integrations_).
## Integration design
When it comes to creating integrations, especially at the level of quality we wanted to achieve, Aspire provides a lot of the groundwork without complexity. Types are generic enough and there's very few `sealed` types, meaning creating custom `Resource` types is easy.
This brings us to the primary challenge as an integration author - consistent defaults with native integrations.
### Util/Shared classes
There are a several classes that live in the [`Shared`](https://github.com/dotnet/aspire/tree/main/src/Shared) and [`Components/Common`](https://github.com/dotnet/aspire/tree/main/src/Components/Common) folders that provide behaviour to integrations. So far, we have wanted to use four of them, `HealthCheckExtensions`, `PasswordGenerator`, `PathNormalizer`, and `VolumeNameGenerator`.
As these types don't belong to assemblies themselves, and are marked as `internal`, we are left with two options:
1. Disregard that functionality.
2. Copy the code into our repo.
So far, we've gone down the route of option two and copied the code across, but this represents a point-in-time snapshot of how the code works and can result in code drift. For example, I noticed while writing this up that our version of `VolumeNameGenerator` is out of date and thus we are not generating volume names consistently with native integrations.
Also, we are looking into how our client integrations can better support the configuration schema (tracking: https://github.com/CommunityToolkit/Aspire/issues/112) and that would require bringing in the `ConfigurationSchemaAttribute` type (as well as the generating tool which has its own issue #3309).
#### Opportunity
There is an opportunity here in which a standalone NuGet package could be created that provides this functionality, or potentially split as two packages, one for client and one for hosting, specifically targeted to integration authors and not general Aspire use (it'd would be a transient dependency though).
As an integration author, I would add a reference to `Aspire.Hosting.Utils` or `Aspire.Client.Utils` (or whatever they are named, naming is hard!), and then I could use `VolumeNameGenerator.CreateVolumeName` and be confident that the names generated in my integration for volumes is consistent with, say, the Postgres hosting integration.
### Client integration configuration schema
I mentioned this above, but currently is it's possible for custom integrations to provide config schema support like native ones do as the tool used to generate that is not shipped as a package/tool for consumption.
#### Opportunity
Deferring to the already existing issue of #3309.
### Type namespace consistency
When going through the Aspire codebase, it's clear that the extension methods for adding integrations either going in the `Aspire.Hosting` (hosting integrations) or `Microsoft.Extensions.Hosting` (client integrations), but it becomes a little more ambigious when we look at the auxillary types, especially `Resource` types.
Here's some examples of the inconsistency:
- [`AzureComsosDBResource`](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs) lives in `Aspire.Hosting`.
- [`IDaprComponentResource`](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.Dapr/IDaprComponentResource.cs) lives in `Aspire.Hosting.Dapr`.
- [`NodeAppResource`](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.NodeJs/NodeAppResource.cs) lives in `Aspire.Hosting`.
- [`PostgresServerResource`](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.PostgreSQL/PostgresServerResource.cs) lives in `Aspire.Hosting.ApplicationModel`, as is `PostgresDatabaseResource`, yet [`PgAdminContainerResource`](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs) is in `Aspire.Hosting.Postgres`.
#### Opportunity
In the Community Toolkit we've normalised on `Aspire.Hosting.ApplicationModel` for resource types but having some official guidance would help ensure that integration authors are able to be consistent in how their types are exposed, especially if there is a desire for extension method interoperability.
## Testing
To ensure a consistent level of quality in the integration we're building it was important to have testing as core principle covering both unit and integration tests. We have a test suite that works around 80% consistently, with issues being more to finding the most efficient way to use GitHub Actions more than anything.
### Testing utils
In a similar thread to creating integrations, there are some testing utilities that we have had to snapshot into our repo so that we can have a similar level of coverage to the native integrations.
**Aspire.Components.Common.Test**
This project contains a class, [`ConformanceTests`](https://github.com/dotnet/aspire/blob/main/tests/Aspire.Components.Common.Tests/ConformanceTests.cs), which is used across all client integrations to validate some base behaviour of the API.
It also contains several XUnit extensions, such as the `RequiredDockerAttribute`, which are useful for conditionally skipping tests.
**Aspire.Hosting.Tests**
This project contains a mix of tests and util types, of particular note are `DockerUtils` and `ManifestUtils`. We have a snapshot of `DockerUtils` in our repo to help with container-based integration tests and we're considering doing the same with `ManifestUtils` to resolve https://github.com/CommunityToolkit/Aspire/issues/115, although with some alterations to workaround the call to the internal method `WriteResourceAsync` on `ManifestPublishingContext`.
#### Opportunity
The opportunity hear is "simple" in having some shipped NuGet packages that contain the utility and test base types, ideally as separate client/hosting packages. Given these are not "general purpose" and wouldn't be needed as transient dependencies, shipping in an Azure Pipelines feed rather than on nuget.org would make sense. This would mean that it's easier to implement testing at the level of the release version of Aspire that an integration targets.
### Documentation
The [testing documentation](https://learn.microsoft.com/dotnet/aspire/testing/write-your-first-test?pivots=mstest) that exists is focused on testing applications using Aspire, not on testing of custom integrations. To write our tests, we've mostly spent time in the Aspire solution and reviewed the tests that exist for native integrations, then adapted them to fit our integrations as best we can.
#### Opportunity
Creating documentation to aid in writing tests for integrations is something that can be a little challenging due to the previous point about often relying on code that is not available outside of the Aspire repo. Resolving that would make documentation easier to produce.
Additionally, I'm unsure if the docs would be best served on Learn, or as part of this repo (in the `/docs` folder), given the problem space, building custom integrations, isn't a super common one that people will be doing, although it could be a continuation of the current docs on custom integrations.
## Summary
Yes, I've outlined a set of new opportunities (_cough_ work _cough_) that would make creating custom integrations easier, but generally speaking, the process is a smooth and approachable one. The fact that we've shipped over a dozen integrations so far, with another half dozen or so in vary states of proposal and review, speaks volumes to how external parties can do this, so kudos everyone.
Contributor guide
Assessment
This issue has not been assessed yet.