Skip to content

Move the business layer implementations to the hosts - #772

Open
KrzysztofPajak wants to merge 1 commit into
developfrom
refactor/host-owns-business-implementations
Open

Move the business layer implementations to the hosts#772
KrzysztofPajak wants to merge 1 commit into
developfrom
refactor/host-owns-business-implementations

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Type: feature

Issue

Grand.Web.Common referenced all nine Grand.Business.* implementations alongside the contracts in Grand.Business.Core, while using zero types from any of them — there is not a single using of an implementation namespace anywhere in the project.

Because Grand.Web builds on Grand.Web.Common, Grand.Web.AdminShared builds on it, and src/Build/Grand.Plugin.props points every plugin at it, the shared web layer and each plugin compiled against the entire business layer for no reason. This is the coupling node described in the architecture notes as the largest in the solution.

Reproduce: dotnet list src/Web/Grand.Web.Common/Grand.Web.Common.csproj reference — twelve project references, nine of them implementations that nothing in the project links.

Solution

The references were never about code. Each implementation carries a Startup/StartupApplication.cs, and TypeSearcher discovers IStartupApplication by walking the assemblies present in the AppDomain — so an implementation the host does not reference is never loaded and its services are never registered. That makes the host, as the composition root, where they belong.

  • New src/Build/Grand.Host.props holds the list once, mirroring what Grand.Plugin.props already does for plugins, with a comment stating why removing an entry costs an entire business area's DI registration while nothing fails at build time.
  • The four hosts (Grand.Web, Grand.Web.Admin, Grand.Web.Store, Grand.Web.Vendor) import it.
  • The nine references are gone from Grand.Web.Common, which drops from twelve project references to three.

Each host's dependency closure is unchanged — the same nine assemblies are referenced, copied to output and written to deps.json. Only the compile-time graph shrinks.

One consequence was not obvious and is worth review attention. Those references were also an implicit version floor for Grand.Web.AdminShared: the Scryber.Core graph outvoted elFinder.Net.* 1.5.0, and once they were gone elFinder won with Newtonsoft.Json 11.0.2 (GHSA-5crp-9r3c-p9vr) and SixLabors.ImageSharp 2.0.0 (GHSA-2cmq-823j-5qj8 and six more) — eight new NU1903/NU1902 warnings. The floor is now stated explicitly next to the existing Microsoft.AspNetCore.Http pin, which exists for the same package graph and the same reason. Both resolve back to exactly what the accident used to produce: 13.0.3 and 3.1.12.

Side effect worth naming: SixLabors.ImageSharp is now a direct reference of Grand.Web.AdminShared rather than only transitive. Nothing changes in the shipped artifacts or the licensing position, but it partly reverses #763. The real fix remains replacing elFinder.

Breaking changes

None. No public interface, view model, widget zone or plugin system name changes. Plugins are unaffected at build and at runtime: Grand.Plugin.props references Grand.Web.Common with ExcludeAssets="all", and no plugin, module or test project uses an implementation namespace — every Grand.Business.*.Tests and Grand.Mapping.Tests references its implementation project directly.

Testing

  1. Delete src/Web/Grand.Web/Plugins and src/Web/Grand.Web/Modules — these are fixed OutputPath directories that the build never cleans, so stale copies otherwise mask the result.
  2. dotnet build ./GrandNode.sln — succeeds; one pre-existing CS0618 warning, no NU19xx.
  3. dotnet list src/Web/Grand.Web.Common/Grand.Web.Common.csproj reference — three references, not twelve.
  4. Confirm the host output is unchanged: each of the four src/Web/*/bin/Debug/net10.0/ directories still contains ten Grand.Business.*.dll (nine implementations plus Grand.Business.Core).
  5. Confirm the version floor held: dotnet list src/Web/Grand.Web.AdminShared/Grand.Web.AdminShared.csproj package --include-transitive shows Newtonsoft.Json 13.0.3 and SixLabors.ImageSharp 3.1.12.
  6. Run the storefront on Kestrel and request /, a category page, and /search?q=book — all 200. This is the real test: a StartupApplication that stopped being discovered fails on a request, not at startup.
  7. Request /admin/login, /vendor/login and /store/login — all 200.
  8. dotnet test on Grand.Web.Common.Tests (15), Grand.Infrastructure.Tests (94) and Grand.Mapping.Tests (238) — all pass.

🤖 Generated with Claude Code

Grand.Web.Common referenced all nine Grand.Business.* implementations
alongside the contracts in Grand.Business.Core, without using a single
type from any of them. Because Grand.Web is built on Grand.Web.Common,
and Grand.Plugin.props points every plugin at it too, the shared web
layer and each plugin compiled against the whole business layer for no
reason.

The references were never about code. Each implementation carries a
Startup/StartupApplication.cs, and TypeSearcher discovers
IStartupApplication by walking the assemblies present in the AppDomain -
so an implementation the host does not reference is never registered.
That makes the host, as the composition root, the place they belong.
src/Build/Grand.Host.props holds the list once, the way
Grand.Plugin.props already does for plugins, and says why removing an
entry costs an entire area's DI registration with nothing failing at
build time.

Each host's dependency closure is unchanged: the same nine assemblies are
still referenced, copied and written to deps.json. Only the compile-time
graph of Grand.Web.Common shrinks, from twelve project references to
three.

One consequence was not obvious. Those references were also an implicit
version floor for Grand.Web.AdminShared: the Scryber.Core graph outvoted
elFinder.Net.* 1.5.0, and without them elFinder won with Newtonsoft.Json
11.0.2 (GHSA-5crp-9r3c-p9vr) and SixLabors.ImageSharp 2.0.0
(GHSA-2cmq-823j-5qj8 and six more). The floor is now stated explicitly
next to the existing Microsoft.AspNetCore.Http pin, which exists for the
same package graph and the same reason, and resolves back to the
13.0.3 and 3.1.12 that the accident used to produce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 9, 2026 20:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants