all the refactor
Build and Publish / BuildAndDeployAmd64 (push) Failing after 5s
Build and Publish / BuildAndDeployArm64 (push) Failing after 6s

This commit is contained in:
2026-06-28 17:51:52 +02:00
parent 7db0d236c7
commit aa8b2bdedc
84 changed files with 4328 additions and 2344 deletions
+80 -10
View File
@@ -66,9 +66,33 @@ func (s *server) loadOrder(w http.ResponseWriter, r *http.Request) (order.OrderI
return id, g, true
}
// checkIdempotency checks if the request is an idempotent retry.
// It returns (idemKey, exists, unlockFn).
// If exists is true, the caller should write the current order grain status and return immediately.
// If exists is false, the caller must defer unlockFn() and continue.
func (s *server) checkIdempotency(w http.ResponseWriter, r *http.Request, id order.OrderId) (string, bool, func()) {
idemKey := r.Header.Get("Idempotency-Key")
if idemKey == "" {
return "", false, func() {}
}
unlock := s.idem.Lock(idemKey)
if _, ok := s.idem.Get(idemKey); ok {
unlock() // Release lock immediately since we are returning cached response
s.logger.Info("idempotency hit for lifecycle endpoint", "key", idemKey, "orderId", id.String())
grain, err := s.applier.Get(r.Context(), uint64(id))
if err != nil {
writeErr(w, http.StatusInternalServerError, err)
return idemKey, true, nil
}
writeJSON(w, http.StatusOK, map[string]any{"orderId": id.String(), "order": grain})
return idemKey, true, nil
}
return idemKey, false, unlock
}
// applyAndRespond applies a mutation and returns the updated order. A rejected
// state transition (handler error) maps to 409 Conflict.
func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id order.OrderId, msg proto.Message) {
func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id order.OrderId, msg proto.Message, idemKey string) {
res, err := s.applier.Apply(r.Context(), uint64(id), msg)
if err != nil {
writeErr(w, http.StatusInternalServerError, err)
@@ -80,6 +104,11 @@ func (s *server) applyAndRespond(w http.ResponseWriter, r *http.Request, id orde
return
}
}
if idemKey != "" {
if err := s.idem.Put(idemKey, uint64(id)); err != nil {
s.logger.Error("idempotency record failed", "key", idemKey, "orderId", id.String(), "err", err)
}
}
grain, err := s.applier.Get(r.Context(), uint64(id))
if err != nil {
writeErr(w, http.StatusInternalServerError, err)
@@ -105,6 +134,12 @@ func (s *server) handleFulfill(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req fulfillReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeErr(w, http.StatusBadRequest, err)
@@ -182,7 +217,7 @@ func (s *server) handleFulfill(w http.ResponseWriter, r *http.Request) {
for _, l := range req.Lines {
msg.Lines = append(msg.Lines, &messages.FulfillmentLine{Reference: l.Reference, Quantity: l.Quantity})
}
s.applyAndRespond(w, r, id, msg)
s.applyAndRespond(w, r, id, msg, idemKey)
}
type exchangeReq struct {
@@ -208,6 +243,12 @@ func (s *server) handleExchange(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req exchangeReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeErr(w, http.StatusBadRequest, err)
@@ -236,7 +277,7 @@ func (s *server) handleExchange(w http.ResponseWriter, r *http.Request) {
TotalTax: l.TotalTax,
})
}
s.applyAndRespond(w, r, id, msg)
s.applyAndRespond(w, r, id, msg, idemKey)
}
type editDetailsReq struct {
@@ -250,6 +291,12 @@ func (s *server) handleEditDetails(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req editDetailsReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeErr(w, http.StatusBadRequest, err)
@@ -261,16 +308,21 @@ func (s *server) handleEditDetails(w http.ResponseWriter, r *http.Request) {
ShippingPrice: req.ShippingPrice,
AtMs: nowMs(),
}
s.applyAndRespond(w, r, id, msg)
s.applyAndRespond(w, r, id, msg, idemKey)
}
func (s *server) handleComplete(w http.ResponseWriter, r *http.Request) {
id, _, ok := s.loadOrder(w, r)
if !ok {
return
}
s.applyAndRespond(w, r, id, &messages.CompleteOrder{AtMs: nowMs()})
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
s.applyAndRespond(w, r, id, &messages.CompleteOrder{AtMs: nowMs()}, idemKey)
}
func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
@@ -278,11 +330,17 @@ func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req struct {
Reason string `json:"reason"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
s.applyAndRespond(w, r, id, &messages.CancelOrder{Reason: req.Reason, AtMs: nowMs()})
s.applyAndRespond(w, r, id, &messages.CancelOrder{Reason: req.Reason, AtMs: nowMs()}, idemKey)
}
type returnReq struct {
@@ -298,6 +356,12 @@ func (s *server) handleReturn(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req returnReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeErr(w, http.StatusBadRequest, err)
@@ -308,7 +372,7 @@ func (s *server) handleReturn(w http.ResponseWriter, r *http.Request) {
for _, l := range req.Lines {
msg.Lines = append(msg.Lines, &messages.FulfillmentLine{Reference: l.Reference, Quantity: l.Quantity})
}
s.applyAndRespond(w, r, id, msg)
s.applyAndRespond(w, r, id, msg, idemKey)
}
func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
@@ -316,13 +380,19 @@ func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
idemKey, exists, unlock := s.checkIdempotency(w, r, id)
if exists {
return
}
defer unlock()
var req struct {
Amount int64 `json:"amount"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
amount := req.Amount
if amount <= 0 {
amount = g.CapturedAmount - g.RefundedAmount // default: full remaining
amount = (g.CapturedAmount - g.RefundedAmount).Int64() // default: full remaining
}
captureRef := capturedRef(g)
ref, err := s.provider.Refund(r.Context(), captureRef, amount)
@@ -335,7 +405,7 @@ func (s *server) handleRefund(w http.ResponseWriter, r *http.Request) {
Amount: ref.Amount,
Reference: ref.Reference,
AtMs: nowMs(),
})
}, idemKey)
}
// --- saga / flow admin (a) ------------------------------------------------