Skip to content

Split MessageProviderService into one service per message area - #977

Open
KrzysztofPajak wants to merge 3 commits into
developfrom
refactor/split-message-provider-service
Open

KrzysztofPajak wants to merge 3 commits into
developfrom
refactor/split-message-provider-service

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Part of the architecture backlog (item S5: god classes, MessageProviderService)
Type: feature

Issue

Grand.Business.Messages/Services/MessageProviderService.cs was 2103 lines behind a 50-member IMessageProviderService. Every caller (order and shipping handlers, account and login controllers, the auction task, the Facebook and Google login plugins) depended on every message the platform sends, and every test had to mock the whole interface.

Solution

  • The typed Send*Message methods move, unchanged and with their comments, into one service per message area, each registered as scoped like before:

    Interface Members Dependencies Covers
    ICustomerMessageProviderService 8 4 registration, welcome, email validation, password recovery, customer note, email token, account deletion, contact us
    IOrderMessageProviderService 16 4 order placed/paid/completed/cancelled/refunded for customer, store owner and vendor, shipments, order notes, gift vouchers
    INewsletterMessageProviderService 2 3 subscription activation and deactivation
    ICatalogMessageProviderService 12 5 email a friend, wishlist, product question and review, quantity below, back in stock, auctions and bids
    IMerchandiseReturnMessageProviderService 4 3 return submitted, status changed, notes
    IVendorMessageProviderService 4 4 vendor application, information change, vendor review, contact vendor
    ICmsMessageProviderService 3 4 blog, knowledgebase article and news comments
  • The lookups they share (store, active template, email account, language) move from protected virtual helpers to IMessageLookupService / MessageLookupService, bodies unchanged.

  • IMessageProviderService keeps only the generic SendNotification; MessageProviderService now depends only on IQueuedEmailService.

  • AddCustomerTokensIfNotNull became an internal LiquidObjectBuilder extension, since five services use it.

  • Each caller used exactly one area, so each now takes that one interface.

  • .ai/skills/message-notification.md describes the new shape.

Breaking changes

  • 49 typed send methods moved from IMessageProviderService to the area interfaces. Code outside this repository that calls one of them must inject the matching I<Area>MessageProviderService; SendNotification stays where it was.
  • A subclass of MessageProviderService that overrides a send method or one of the protected helpers (GetStore, GetMessageTemplate, GetEmailAccountOfMessageTemplate, EnsureLanguageIsActive) must move that code to the new service or to MessageLookupService. The MessageProviderService constructor now takes only IQueuedEmailService.
  • No message template name, token, route, permission, setting or localization key changes. No behavior change intended.

Testing

  1. dotnet build GrandNode.slnx: no errors.
  2. dotnet test for Grand.Business.Messages.Tests (53, 4 new), Grand.Business.Checkout.Tests (304), Grand.Modules.Tests (397), Grand.Web.Tests (156), Grand.Web.Admin.Tests (1596), Grand.Web.Store.Tests (181), Grand.Web.Vendor.Tests (53) pass, the same counts as before. Only mock types and field names changed in existing tests; the new tests check that Order, Newsletter, MerchandiseReturn and Cms queue an email.
  3. Fresh install with sample data; in Admin turn on: product review and ask-question notifications (Catalog settings), blog and news comment notifications (Content), new customer registration notification (Customer), "Allow customers to apply for vendor account" (Vendor). Then check System > Queued emails after each step:
    • Customer: register a customer (new customer + welcome), password recovery, contact us form.
    • Newsletter: subscribe in the footer (activation email).
    • Catalog: on a product page email a friend, ask a question, write a review.
    • Vendor: apply for a vendor account.
    • Cms: comment on a blog post and on a news item.
    • Order: check out with Cash on Delivery (order placed to customer and store owner); in Admin add an order note visible to the customer, create a shipment, set it as shipped and as delivered.
    • Merchandise return: as the customer return the delivered item (submitted to customer and store owner); in Admin add a note visible to the customer and save the return with "Notify customer".
  4. Open the Store and Vendor login pages and the Admin customer, order, gift voucher and return lists: 200, no errors in the log.

support added 2 commits October 22, 2026 20:45
MessageProviderService was a 2100-line class behind a 50-member interface,
so every caller (order handlers, account controllers, scheduled tasks,
plugins) depended on every message the platform sends.

The typed Send*Message methods move verbatim into seven services:
Customer, Order, Newsletter, Catalog, MerchandiseReturn, Vendor and Cms
(I<Area>MessageProviderService). The lookups they share (store, active
template, email account, language) move to IMessageLookupService, and
IMessageProviderService keeps only the generic SendNotification. Each
caller now takes the one interface it uses.

Breaking change: code outside the repository that calls a typed send
method on IMessageProviderService, or overrides one in a subclass of
MessageProviderService, must switch to the area interface. Template
names, tokens and DI lifetimes are unchanged.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 19:33

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.

//store in database
if (_commonSettings.StoreInDatabaseContactUsForm)
await _mediator.Send(new InsertContactUsCommand {
CustomerId = customer.Id,
- Skip deleted bidders in the auction lost/outbid/store-owner sends and
  resolve the language per losing bidder instead of reusing the first one
- Tolerate a null customer in the back-in-stock send and a missing vendor
  in the vendor review send
- Publish MessageTokensAdded and use the customer's store in the
  customer-delete store owner notification
- Resolve stores through IMessageLookupService.GetStore in the gift voucher
  and auction-expired sends, dropping IStoreService from the order and
  catalog services
- Point the notification skill and agent at the per-area services and
  BuildAsync
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