refactor
This commit is contained in:
+75
-23
@@ -12,6 +12,7 @@ import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -36,19 +37,20 @@ import (
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pool *actor.SimpleGrainPool[order.OrderGrain]
|
||||
applier *orderedApplier
|
||||
storage *actor.DiskStorage[order.OrderGrain]
|
||||
reg actor.MutationRegistry
|
||||
engine *flow.Engine
|
||||
freg *flow.Registry
|
||||
flows *flowStore
|
||||
provider order.PaymentProvider
|
||||
taxProvider tax.Provider
|
||||
defaultFlow string
|
||||
dataDir string
|
||||
idem *idempotency.Store
|
||||
logger *slog.Logger
|
||||
pool *actor.SimpleGrainPool[order.OrderGrain]
|
||||
applier *orderedApplier
|
||||
storage *actor.DiskStorage[order.OrderGrain]
|
||||
reg actor.MutationRegistry
|
||||
engine *flow.Engine
|
||||
freg *flow.Registry
|
||||
flows *flowStore
|
||||
provider order.PaymentProvider
|
||||
taxProvider tax.Provider
|
||||
defaultFlow string
|
||||
dataDir string
|
||||
idem *idempotency.Store
|
||||
logger *slog.Logger
|
||||
inventoryReservations order.InventoryReservationService
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -101,6 +103,18 @@ func main() {
|
||||
// the broker (publisher is nil without AMQP — the hook then errors at run
|
||||
// time, which is logged, not fatal).
|
||||
amqpURL := os.Getenv("AMQP_URL")
|
||||
emailTemplates, err := order.LoadEmailTemplates(config.EnvString("ORDER_EMAIL_TEMPLATES", "data/order-email-templates"))
|
||||
if err != nil {
|
||||
log.Fatalf("load email templates: %v", err)
|
||||
}
|
||||
emailSender, err := selectEmailSender(logger)
|
||||
if err != nil {
|
||||
log.Fatalf("configure email sender: %v", err)
|
||||
}
|
||||
inventoryReservations, err := selectInventoryReservationService()
|
||||
if err != nil {
|
||||
log.Fatalf("configure inventory reservation service: %v", err)
|
||||
}
|
||||
var emitPub order.Publisher
|
||||
if amqpURL != "" {
|
||||
// The amqp_emit hook writes to a durable outbox rather than the broker
|
||||
@@ -138,13 +152,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
freg := flow.NewRegistry()
|
||||
flow.RegisterBuiltinHooks(freg)
|
||||
order.RegisterFlowActions(freg, applier, provider)
|
||||
order.RegisterEmitHook(freg, emitPub)
|
||||
// order.created domain event → durable outbox → "order" topic exchange.
|
||||
// Reactors: inventory commit, fulfillment allocation.
|
||||
order.RegisterOrderCreatedEmit(freg, applier, emitPub, "order", "order")
|
||||
freg := buildFlowRegistry(applier, provider, inventoryReservations, emitPub, emailSender, emailTemplates, logger)
|
||||
engine := flow.NewEngine(freg, logger)
|
||||
|
||||
def, err := order.EmbeddedFlow("place-and-pay")
|
||||
@@ -171,6 +179,7 @@ func main() {
|
||||
engine: engine, freg: freg, flows: flows, provider: provider,
|
||||
taxProvider: taxProvider,
|
||||
defaultFlow: def.Name, dataDir: dataDir, idem: idem, logger: logger,
|
||||
inventoryReservations: inventoryReservations,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -276,8 +285,9 @@ type lineReq struct {
|
||||
Sku string `json:"sku"`
|
||||
Name string `json:"name"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
UnitPrice int64 `json:"unitPrice"` // inc-vat, minor units
|
||||
TaxRate int32 `json:"taxRate"` // basis points (2500 = 25%)
|
||||
UnitPrice int64 `json:"unitPrice"` // inc-vat, minor units
|
||||
TaxRate int32 `json:"taxRate"` // basis points (2500 = 25%)
|
||||
DropShip bool `json:"drop_ship,omitempty"`
|
||||
Location string `json:"location,omitempty"` // inventory commit location / store id
|
||||
}
|
||||
|
||||
@@ -384,6 +394,7 @@ func (s *server) buildPlaceOrder(id order.OrderId, req *checkoutReq) *messages.P
|
||||
TaxRate: l.TaxRate,
|
||||
TotalAmount: lineTotal,
|
||||
TotalTax: lineTax,
|
||||
DropShip: l.DropShip,
|
||||
})
|
||||
}
|
||||
po.TotalAmount = total
|
||||
@@ -502,7 +513,6 @@ func selectTaxProvider() tax.Provider {
|
||||
// the dependency-free mock; set PAYMENT_PROVIDER=stripe + STRIPE_SECRET_KEY for
|
||||
// real Stripe (STRIPE_API_BASE / STRIPE_PAYMENT_METHOD optionally override).
|
||||
func selectProvider(logger *slog.Logger) order.PaymentProvider {
|
||||
|
||||
if os.Getenv("PAYMENT_PROVIDER") == "stripe" {
|
||||
key := os.Getenv("STRIPE_SECRET_KEY")
|
||||
if key == "" {
|
||||
@@ -515,6 +525,48 @@ func selectProvider(logger *slog.Logger) order.PaymentProvider {
|
||||
return order.NewMockProvider()
|
||||
}
|
||||
|
||||
// selectEmailSender picks the flow-email transport. SMTP is the only sender
|
||||
// implementation for now; when no SMTP host is configured the hook still exists
|
||||
// in capabilities but errors at run time if a flow tries to use it.
|
||||
func selectEmailSender(logger *slog.Logger) (order.EmailSender, error) {
|
||||
kind := config.EnvString("ORDER_EMAIL_SENDER", "")
|
||||
if kind == "" && os.Getenv("ORDER_EMAIL_SMTP_HOST") == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if kind == "" {
|
||||
kind = "smtp"
|
||||
}
|
||||
if kind != "smtp" {
|
||||
return nil, fmt.Errorf("unsupported ORDER_EMAIL_SENDER %q", kind)
|
||||
}
|
||||
|
||||
fromAddr := strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_ADDRESS"))
|
||||
if fromAddr == "" {
|
||||
return nil, fmt.Errorf("ORDER_EMAIL_FROM_ADDRESS is required when email sender is configured")
|
||||
}
|
||||
from := mail.Address{
|
||||
Name: strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_NAME")),
|
||||
Address: fromAddr,
|
||||
}
|
||||
sender, err := order.NewSMTPEmailSender(order.SMTPEmailSenderConfig{
|
||||
Host: config.EnvString("ORDER_EMAIL_SMTP_HOST", ""),
|
||||
Port: config.EnvInt("ORDER_EMAIL_SMTP_PORT", 587, func(n int) bool { return n > 0 }),
|
||||
Username: os.Getenv("ORDER_EMAIL_SMTP_USERNAME"),
|
||||
Password: os.Getenv("ORDER_EMAIL_SMTP_PASSWORD"),
|
||||
DefaultFrom: from,
|
||||
Timeout: config.EnvDuration("ORDER_EMAIL_SMTP_TIMEOUT", 10*time.Second),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("email sender enabled", "kind", kind, "host", config.EnvString("ORDER_EMAIL_SMTP_HOST", ""), "from", from.Address)
|
||||
return sender, nil
|
||||
}
|
||||
|
||||
func selectInventoryReservationService() (order.InventoryReservationService, error) {
|
||||
return order.NewHTTPInventoryReservationService(config.EnvString("INVENTORY_URL", "http://localhost:8080"), nil)
|
||||
}
|
||||
|
||||
// loadUCPOrderSigner loads the UCP ECDSA signing key from the path specified in
|
||||
// the UCP_SIGNING_KEY_PATH environment variable. Returns nil if unset or unreadable.
|
||||
func loadUCPOrderSigner() *ucp.SigningConfig {
|
||||
|
||||
Reference in New Issue
Block a user