π§© Add Your First Command
Letβs implement your first Potato command: CreateUserCommand. This command allows you to add new users to the tree structure under the root node.
π Follow the Potato feature-folder layout:
Potato.Example.Application/Features/Users/Create/
π‘ Protip: Add a
Usings.csfile to your project root to eliminate repetitive imports:global using static LanguageExt.Prelude; global using static Potato.PotatoModule; global using LanguageExt; global using Potato; global using Potato.Examples.Domain;
π CreateUserCommand.cs
namespace Potato.Examples.Application;
/// <summary>
/// Command to create a new user node under the root.
/// </summary>
public record CreateUserCommand(string Username) : PotatoCommand<UserNode>;
π CreateUserCommand.Validator.cs
using FluentValidation;
namespace Potato.Examples.Application;
/// <summary>
/// Validator for the <see cref="CreateUserCommand"/>.
/// </summary>
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Username)
.NotEmpty()
.MinimumLength(3)
.MaximumLength(32);
}
}
π CreateUserCommand.Handler.cs
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Potato.Examples.Application;
/// <summary>
/// Handles the creation of a new <see cref="UserNode"/> in the tree.
/// </summary>
public class CreateUserCommandHandler
(
ILogger<CreateUserCommandHandler> logger,
PotatoRepository<Node> repository
): PotatoCommandHandler<CreateUserCommand, UserNode>
{
public override async Task<Either<PotatoFailure, UserNode>> Handle(CreateUserCommand command, CancellationToken cancellationToken)
{
try
{
using Activity? activity = Activity.Current?.Source.StartActivity();
logger.LogCommandRecieved(command);
var user = new UserNode(
Id: Guid.NewGuid(),
ParentId: RootNode.Instance.GetIdOrThrow(),
Username: command.Username
);
return await repository.Create(user)
.MapAsync(n => (UserNode)n)
.HandlerFinished(logger, command);
}
catch (Exception e)
{
logger.LogCommandFailed(command, e);
return UnexpectedFailure(e);
}
}
}
π CreateUserCommand.Extension.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Potato.Examples.Application;
namespace Microsoft.Extensions.DependencyInjection;
public static class CreateUserCommandExtension
{
public static RouteHandlerBuilder MapCreateUser(this WebApplication app)
{
return app.MapPut("users/create", async ([FromBody] CreateUserCommand command, [FromServices] PotatoCommandExecutor executor) =>
{
return await executor.Execute<CreateUserCommand, UserNode>(command);
});
}
}
π Register the route in Program.cs:
app.MapCreateUser();
π Potato.Example.Application.http
Use this HTTP request file to test the endpoint via REST Client in VS Code:
@host = http://localhost
@port = 5122
PUT {{host}}:{{port}}/users/create
Content-Type: application/json
{
"username": "JohnDoe"
}
###
β Summary
You now have:
- β
A
CreateUserCommandto add users to the tree - β
Validation rules via
FluentValidation - β
A command handler that inserts the
UserNodeunder the root - β A minimal API endpoint to expose the command
- β
A
.httpfile for testing - β A global failure handler for proper HTTP responses
- β
Cleaner code using
Usings.cs
β‘οΈ Next: Create your first query to retrieve a user by ID.