Complete refactor to new grpc control plane and only http proxy for carts #4

Merged
mats merged 75 commits from refactor/http-proxy into main 2025-10-14 22:31:28 +02:00
4 changed files with 29 additions and 128 deletions
Showing only changes of commit f8c8ad56c7 - Show all commits

View File

@@ -113,7 +113,7 @@ metadata:
arch: arm64
name: cart-actor-arm64
spec:
replicas: 0
replicas: 3
selector:
matchLabels:
app: cart-actor

102
frames.go
View File

@@ -1,102 +0,0 @@
package main
// Minimal frame abstractions retained after removal of the legacy TCP/frame
// networking layer. These types remain only to avoid a wide cascading refactor
// across existing grain / pool logic that still constructs and passes
// FrameWithPayload objects internally.
//
// The original responsibilities this replaces:
// - Binary framing, checksums, network IO
// - Distinction between request / reply frame types
//
// What remains:
// - A light weight container (FrameWithPayload) used as an inprocess
// envelope for status code + typed marker + payload bytes (JSON or proto).
// - Message / status constants referenced in existing code paths.
//
// Recommended future cleanup (postmigration):
// - Remove FrameType entirely and replace with enumerated semantic results
// or error values.
// - Replace FrameWithPayload with a struct { Status int; Data []byte }.
// - Remove remote_* reply type branching once all callers rely on gRPC
// status + strongly typed responses.
//
// For now we keep this minimal surface to keep the gRPC migration focused.
type (
// FrameType is a symbolic identifier carried through existing code paths.
// No ordering or bit semantics are required anymore.
FrameType uint32
StatusCode uint32
)
type Frame struct {
Type FrameType
StatusCode StatusCode
Length uint32
// Checksum retained for compatibility; no longer validated.
Checksum uint32
}
// FrameWithPayload wraps a Frame with an opaque payload.
// Payload usually contains JSON encoded cart state or an error message.
type FrameWithPayload struct {
Frame
Payload []byte
}
// -----------------------------------------------------------------------------
// Legacy Frame Type Constants (minimal subset still referenced)
// -----------------------------------------------------------------------------
const (
RemoteGetState = FrameType(0x01)
RemoteHandleMutation = FrameType(0x02)
ResponseBody = FrameType(0x03) // (rarely used; kept for completeness)
RemoteGetStateReply = FrameType(0x04)
RemoteHandleMutationReply = FrameType(0x05)
RemoteCreateOrderReply = FrameType(0x06)
)
// MakeFrameWithPayload constructs an inprocess frame wrapper.
// Length & Checksum are filled for backward compatibility (no validation logic
// depends on the checksum anymore).
func MakeFrameWithPayload(msg FrameType, statusCode StatusCode, payload []byte) FrameWithPayload {
length := uint32(len(payload))
return FrameWithPayload{
Frame: Frame{
Type: msg,
StatusCode: statusCode,
Length: length,
Checksum: (uint32(msg) + uint32(statusCode) + length) / 8, // simple legacy formula
},
Payload: payload,
}
}
// Clone creates a shallow copy of the frame, duplicating the payload slice.
func (f *FrameWithPayload) Clone() *FrameWithPayload {
if f == nil {
return nil
}
cp := make([]byte, len(f.Payload))
copy(cp, f.Payload)
return &FrameWithPayload{
Frame: f.Frame,
Payload: cp,
}
}
// NewErrorFrame helper for creating an error frame with a textual payload.
func NewErrorFrame(msg FrameType, code StatusCode, err error) FrameWithPayload {
var b []byte
if err != nil {
b = []byte(err.Error())
}
return MakeFrameWithPayload(msg, code, b)
}
// IsSuccess returns true if the status code indicates success in the
// conventional HTTP style range (200299). This mirrors previous usage patterns.
func (f *FrameWithPayload) IsSuccess() bool {
return f != nil && f.StatusCode >= 200 && f.StatusCode < 300
}

View File

@@ -249,20 +249,20 @@ func main() {
return
}
cartId := ToCartId(cookie.Value)
_, err = syncedServer.pool.Apply(cartId, getCheckoutOrder(r.Host, cartId))
order, err = syncedServer.CreateOrUpdateCheckout(r.Host, cartId)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
}
// v2: Apply now returns *CartGrain; order creation handled inside grain (no payload to unmarshal)
} else {
prevOrder, err := KlarnaInstance.GetOrder(orderId)
order, err = KlarnaInstance.GetOrder(orderId)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
order = prevOrder
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")

View File

@@ -216,48 +216,51 @@ func (s *PoolServer) HandleConfirmation(w http.ResponseWriter, r *http.Request,
return json.NewEncoder(w).Encode(order)
}
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
// Build checkout meta (URLs derived from host)
func (s *PoolServer) CreateOrUpdateCheckout(host string, id CartId) (*CheckoutOrder, error) {
meta := &CheckoutMeta{
Terms: fmt.Sprintf("https://%s/terms", r.Host),
Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", r.Host),
Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", r.Host),
Validation: fmt.Sprintf("https://%s/validate", r.Host),
Push: fmt.Sprintf("https://%s/push?order_id={checkout.order.id}", r.Host),
Country: getCountryFromHost(r.Host),
Terms: fmt.Sprintf("https://%s/terms", host),
Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", host),
Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", host),
Validation: fmt.Sprintf("https://%s/validate", host),
Push: fmt.Sprintf("https://%s/push?order_id={checkout.order.id}", host),
Country: getCountryFromHost(host),
}
// Get current grain state (may be local or remote)
grain, err := s.pool.Get(id)
if err != nil {
return err
return nil, err
}
// Build pure checkout payload
payload, _, err := BuildCheckoutOrderPayload(grain, meta)
if err != nil {
return err
return nil, err
}
// Call Klarna (create or update)
var klarnaOrder *CheckoutOrder
if grain.OrderReference != "" {
klarnaOrder, err = KlarnaInstance.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
return KlarnaInstance.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
} else {
klarnaOrder, err = KlarnaInstance.CreateOrder(bytes.NewReader(payload))
return KlarnaInstance.CreateOrder(bytes.NewReader(payload))
}
}
func (s *PoolServer) ApplyCheckoutStarted(klarnaOrder *CheckoutOrder, id CartId) (*CartGrain, error) {
// Persist initialization state via mutation (best-effort)
return s.pool.Apply(id, &messages.InitializeCheckout{
OrderId: klarnaOrder.ID,
Status: klarnaOrder.Status,
PaymentInProgress: true,
})
}
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
klarnaOrder, err := s.CreateOrUpdateCheckout(r.Host, id)
if err != nil {
return err
}
// Persist initialization state via mutation (best-effort)
if _, applyErr := s.pool.Apply(id, &messages.InitializeCheckout{
OrderId: klarnaOrder.ID,
Status: klarnaOrder.Status,
PaymentInProgress: true,
}); applyErr != nil {
log.Printf("InitializeCheckout apply error: %v", applyErr)
}
s.ApplyCheckoutStarted(klarnaOrder, id)
w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(klarnaOrder)