# Architecture overview

For developers

GrandNode is an ASP.NET Core application backed by MongoDB. The source is one solution, `GrandNode.slnx`, split into layers that depend only inward: the domain knows nothing about the database, the business layer knows nothing about HTTP, and the core never references a plugin. This article is the map you need before you change or extend anything.

### Solution layout

Everything lives under `src/`:

| Folder | What it contains |
|---|---|
| `src/Core` | `Grand.Domain` (entities and settings classes, no dependencies), `Grand.Data` (the `IRepository<T>` abstraction with MongoDB and LiteDB implementations), `Grand.Infrastructure` (startup, plugin and module loading, caching, migrations infrastructure), `Grand.Mediator`, `Grand.Mapping`, `Grand.SharedKernel`. |
| `src/Business` | `Grand.Business.Core` holds the interfaces, commands and queries; `Grand.Business.Catalog`, `.Checkout`, `.Customers`, `.Marketing`, `.Messages`, `.Cms`, `.Common`, `.Authentication` and `.Storage` hold the implementations. |
| `src/Web` | `Grand.Web` (the storefront and the host process), `Grand.Web.Admin` (administrator panel), `Grand.Web.Store` (store manager panel), `Grand.Web.Vendor` (vendor panel), `Grand.Web.AdminShared` (models, validators and mappings shared by the panels), `Grand.Web.Common` (base controllers, tag helpers, themes, middleware) and `Grand.SharedUIResources` (panel assets). |
| `src/Modules` | Application modules switched on in configuration: `Grand.Module.Installer`, `Grand.Module.Migration`, `Grand.Module.ScheduledTasks` and `Grand.Module.Api`. |
| `src/Plugins` | Installable providers and themes: `Payments.*`, `Shipping.*`, `Tax.*`, `Widgets.*`, `DiscountRules.*`, `Authentication.*`, `ExchangeRate.*`, `Theme.*`. |
| `src/Tests` | MSTest projects, one per production project (`Grand.Business.Catalog.Tests`, `Grand.Web.Admin.Tests` and so on). |
| `src/Aspire` | A .NET Aspire host that starts MongoDB, Redis and two replicas of the web app for local multi-instance testing. |

Modules and plugins are not referenced by `Grand.Web`. Each one builds itself into `src/Web/Grand.Web/Modules/<name>` or `src/Web/Grand.Web/Plugins/<SystemName>`, and the host discovers them at startup.

### Layering

```
Grand.Web / Grand.Module.Api     HTTP, controllers, view models
Grand.Business.*                 use cases, services, validators
Grand.Domain                     entities (no dependencies)
Grand.Data / Grand.Infrastructure  data access, caching, startup
Grand.SharedKernel               primitives shared by every layer
```

- Controllers hold no business logic. They read the input, send a mediator request and return a result.
- Business services depend on `IRepository<T>`, never on a MongoDB collection.
- Service registrations live in an `IStartupApplication` class in the project that owns them, not in `Program.cs`.
- A core, business or web project never references a plugin. The core defines an interface; a plugin registers an implementation.

### Startup and IStartupApplication

`Program.cs` in `Grand.Web` is deliberately short. `StartupBase` scans the loaded assemblies, including installed plugins and enabled modules, for classes implementing `IStartupApplication` and runs them ordered by `Priority`:

```
public interface IStartupApplication
{
    void ConfigureServices(IServiceCollection services, IConfiguration configuration);
    void Configure(WebApplication application, IWebHostEnvironment webHostEnvironment);
    int Priority { get; }
    bool BeforeConfigure { get; }
}
```

Lower priorities run first. The core uses negative values for URL rewriting and forwarded headers, `0` for core services, `500` for authentication and the context middleware and `1000` for endpoint routing. Plugins conventionally use `10`. `Configure` runs in two passes: every startup with `BeforeConfigure = true` first, then the rest.

On each request the `ContextMiddleware` resolves the current store first and then the current customer, language and currency, and exposes them through `IContextAccessor` (`StoreContext` and `WorkContext`). Code that runs outside a request, such as scheduled tasks and migrations, has no work context and must receive the store or customer explicitly.

### Data access: IRepository

All persistence goes through `IRepository<T>` from `Grand.Data`. It exposes `Table` as an `IQueryable<T>` for LINQ queries and async writes such as `InsertAsync`, `UpdateAsync`, `DeleteAsync`, plus partial updates (`UpdateField`, `IncField`, `AddToCollectionField`) that change one field without rewriting the document. The MongoDB implementation is the default; a LiteDB implementation exists for embedded scenarios.

```
var query = _productRepository.Table
    .Where(p => p.Published && p.VendorId == vendorId)
    .OrderBy(p => p.DisplayOrder);

return await _productRepository.PagedAsync(query, pageIndex, pageSize);
```

The repository also offers async helpers that work for both providers: `ToListAsync`, `FirstOrDefaultAsync`, `CountAsync`, `AnyAsync` and `PagedAsync`.

MongoDB documents use PascalCase field names (`Name`, not `name`), which matters when you query the database directly in `mongosh`.

### Mediator: commands, queries and events

`Grand.Mediator` is an in-house mediator with a MediatR-compatible API (`IRequest`, `IRequestHandler`, `INotification`, `INotificationHandler`, `IMediator`); the MediatR package itself is not used. Request types that cross projects are defined in `Grand.Business.Core`; their handlers live in the business project that owns the feature. The storefront has its own view-model requests in `Grand.Web/Features/Handlers` and `Grand.Web/Commands/Handler`.

```
public class GetSuggestedProductsQuery : IRequest<IList<Product>>
{
    public string[] CustomerTagIds { get; set; }
    public int ProductsNumber { get; set; }
}

// in a controller or another handler
var products = await _mediator.Send(new GetSuggestedProductsQuery {
    CustomerTagIds = customer.CustomerTags.ToArray(), ProductsNumber = 4 });
```

After every write, services publish an entity event so other code can react without the service knowing about it:

```
await _repository.UpdateAsync(entity);
await _cache.RemoveByPrefix(CacheKey.TAXCATEGORIES_PATTERN_KEY);
await _mediator.EntityUpdated(entity);
```

Handlers implement `INotificationHandler<EntityUpdated<T>>` (or `EntityInserted`, `EntityDeleted`). Plugins use the same mechanism to hook into the platform, for example to react when an order is placed.

### Caching

`ICacheBase` is the only caching abstraction. It is an in-memory cache, optionally synchronised across instances through Redis publish/subscribe (`Redis:RedisPubSubEnabled` in `appsettings.json`). Caching belongs in business services, around the repository call, never in controllers or handlers. Keys are constants on the `CacheKey` class and must include every variable that changes the result, above all the store id and language id.

```
public async Task<IList<TaxCategory>> GetAllTaxCategories(string storeId = "")
{
    var key = string.Format(CacheKey.TAXCATEGORIES_ALL_KEY, storeId);
    return await _cacheBase.GetAsync(key, async () =>
    {
        var query = _taxCategoryRepository.Table.AsQueryable();
        if (!string.IsNullOrEmpty(storeId))
            query = query.Where(tc => tc.StoreId == storeId || string.IsNullOrEmpty(tc.StoreId));
        return await _taxCategoryRepository.ToListAsync(query.OrderBy(tc => tc.DisplayOrder));
    });
}
```

Every insert, update and delete clears the family prefix with `RemoveByPrefix`, otherwise the storefront keeps serving stale data.

### From request to page

```
Controller action
  -> IMediator.Send(query)            Grand.Web/Features/Handlers
     -> business service               Grand.Business.*
        -> ICacheBase.GetAsync          Grand.Infrastructure/Caching
           -> IRepository<T> -> MongoDB   Grand.Data
  -> View(model)
     -> theme view locations (IThemeView), widget zones
```

### Related

- [Development setup](https://grandnode.com/developers-development-setup)
- [Writing a plugin](https://grandnode.com/developers-writing-a-plugin)
- [Creating a theme](https://grandnode.com/developers-creating-a-theme)
- [The AI Agent Kit](https://grandnode.com/developers-ai-agent-kit) — the repository's `.ai/knowledge` folder goes deeper on every section above.

- [Web page](https://grandnode.com/developers-architecture-overview)
