Skip to main content
Technical ManualLooking for step-by-step instructions? In the User Manual: Finding Your Way Around

Architecture

Al-Mohaseb is a set of Orchard Core modules hosted by one ASP.NET Core application. Each business is its own tenant with its own database; the books underneath every screen are a double-entry general ledger; and heavy work runs as durable background jobs.

Project layers​

Each area of the product is split into up to three projects. Namespaces follow folders, and each file holds one public type named after the file.

LayerProjectHoldsDepends on Orchard UI
AbstractionsCrestApps.Almohaseb.<Area>.AbstractionsContracts implemented by separate provider projects, and plain modelsNo
CoreCrestApps.Almohaseb.<Area>.CoreModels, YesSql indexes, stores, services and static permission classes. Testable without the Orchard UI.No
ModuleCrestApps.Almohaseb.<Area>Orchard wiring only: Manifest.cs, startups, display drivers, controllers, views, migrations, recipesYes

The foundation area has no <Area> suffix: the module is CrestApps.Almohaseb and its library is CrestApps.Almohaseb.Core. Primitives with no Orchard dependency, such as Money, FiscalCalendar and EmployerIdentificationNumber, live in CrestApps.Almohaseb.Abstractions under the root namespace CrestApps.Almohaseb. See Foundation.

The host, CrestApps.Almohaseb.Web, references every module: Al-Mohaseb's, CrestApps' and Orchard Core's.

builder.Services
.AddOrchardCms(orchardCoreBuilder => orchardCoreBuilder
// Installs the tenant hierarchy guards in every tenant. They do nothing in a tenant that is neither a firm
// (parent) nor a firm-managed client (child).
.AddTenantHierarchy());

Features​

  • A feature is declared only when a person should choose it. Accounting is one; the foundation, the admin UI and jobs are not.
  • Shared services live in features marked EnabledByDependencyOnly = true. They turn on when a feature that needs them does.
  • Integrations between features are [RequireFeatures(...)] startups, so a behaviour switches on only when both sides are enabled.
  • Tax years, states and POS brands are never features. They are data and behaviours inside a feature.
  • A recipe cannot enable an EnabledByDependencyOnly feature directly; it enables a managed feature that depends on it.

The Feature ID Reference lists every feature.

Tenancy​

TenantWhoSet up with
DefaultThe platform: tenant management and the tenant hierarchy that turns tenants into firmsalmohaseb-platform
FirmAn accounting firm. A parent tenant in the CrestApps tenant hierarchy.(planned)
Firm-managed businessA business served by a firm. A child tenant with its own database.A business recipe
Standalone businessA business on its own. A plain Orchard Core tenant with no hierarchy feature.A business recipe

Every business tenant runs the same bookkeeping modules, whether or not it belongs to a firm. The host calls AddTenantHierarchy() so the hierarchy guards are installed everywhere; they do nothing in a tenant that is neither a parent nor a child.

Persistence​

  • YesSql only. No Entity Framework and no NHibernate.
  • High-volume and regulated records are YesSql documents in a dedicated collection per bounded context (for example Jobs), stored through CrestApps DocumentCatalog<T, TIndex> stores.
  • Content items are used only for master data that editors should be able to extend with parts and fields.
  • Index migrations use literal column names (never DocumentId) and .WithLength(26) for ids.
  • Queries must translate to SQL. No unbounded ListAsync(): lists page in the database.

Money, dates and time​

  • Money is decimal, wrapped in Money with an explicit RoundingPolicy. double is never used for money.
  • The current time comes from IClock, never DateTime.UtcNow.
  • Business dates come from IBusinessCalendar, which applies the tenant's time zone and the business day's start time. A convenience store whose business day starts at 04:00 books a 02:30 sale on the previous day. See Foundation.

Permissions​

  • Each area has a static public static class <Area>Permissions in its Core project, and an internal sealed class PermissionProvider in its module.
  • Default stereotypes give every permission to the Administrator role only (OrchardCoreConstants.Roles.Administrator).
  • Every controller action and endpoint calls IAuthorizationService.AuthorizeAsync and returns Forbid() when it fails. Menus only hide items; they are never the security boundary.

The User Manual's Roles and permissions page lists every permission by its display name.

Admin UI​

  • The product UI is the Orchard Core admin (TheAdmin). There is no single-page app.
  • HTML screens are MVC [Admin] controllers; JSON is served by Minimal APIs.
  • Every list renders through AdminList with AdminListContext<TEntry>, which gives the List and Grid layouts, bulk actions and the page-size pager.
  • Every list shares one search and filter pattern. See Admin UI.
  • Editors are display drivers with ocat-* form classes. Menus use AdminNavigationProvider under the existing groups.
  • User-facing strings go through S[...] and H[...] so they can be localized.

Background processing​

Work that touches more than about 500 documents or takes more than about 2 seconds runs as a durable job in bounded batches, with one session per batch, rather than in a web request. Orchard Core background tasks are used only as schedulers and sweepers. See Jobs.

Security​

  • No secrets in code, recipes or tests.
  • Tenant secrets and sensitive identifiers (such as the EIN) are protected at rest with ASP.NET Core Data Protection and shown masked. Host secrets belong in a key vault.
  • Deployment plans carry protected values with the source tenant's key ring, so a target tenant cannot read them; re-enter the EIN on the target tenant after an import. See Foundation.