Skip to content

Modular Ops

Modules let you define a command set once and mount it at multiple points in your command graph — DRY at the routing level.

A module is a method (or class) that registers commands onto an IReplMap:

void RegisterCrudCommands<T>(IReplMap map, IStore<T> store) where T : IEntity
{
map.Map("list", () => store.All());
map.Map("show {id:int}", (int id) => store.Get(id));
map.Map("delete {id:int}", (int id) =>
{
store.Delete(id);
return Results.Success($"Deleted {id}.");
});
}
app.Context("contacts", contacts =>
RegisterCrudCommands(contacts, contactStore));
app.Context("products", products =>
RegisterCrudCommands(products, productStore));

Both contacts and products now expose the same list, show, and delete commands against their respective stores.

Register a generic store and let the DI container resolve per-type:

builder.Services.AddSingleton(typeof(IStore<>), typeof(InMemoryStore<>));
app.Context("contacts", contacts =>
{
contacts.Map("list", static (IStore<Contact> store) => store.All());
contacts.Map("show {id:int}", static (int id, IStore<Contact> store) => store.Get(id));
});

Mount a module and hide it from automation surfaces:

app.Context("admin", admin =>
{
admin.Map("purge-all", static (IStore<Contact> store) => store.DeleteAll());
})
.AutomationHidden();

Reusing the same module under multiple contexts

Section titled “Reusing the same module under multiple contexts”
void RegisterAuditLog(IReplMap map, string entityType)
{
map.Map("log", (IAuditLog log) => log.GetEntries(entityType));
map.Map("log clear", (IAuditLog log) =>
{
log.Clear(entityType);
return Results.Success("Log cleared.");
});
}
app.Context("contacts", contacts =>
{
RegisterCrudCommands(contacts, contactStore);
RegisterAuditLog(contacts, "contact");
});
app.Context("products", products =>
{
RegisterCrudCommands(products, productStore);
RegisterAuditLog(products, "product");
});

This composes cleanly: each context has both CRUD and audit commands without copy-pasting routes.


  • Modules — complete MapModule API: DI-resolved generic form, instance form, conditional and DI-injected predicates, dynamic context routes.
  • Dependency Injection — service lifetimes and how services are resolved inside module constructors and handlers.
  • Best Practices — when to extract a module and how to keep the command graph maintainable.