mirror of
https://github.com/hanzoai/papers.git
synced 2026-10-22 17:20:17 +00:00
141 lines
5.2 KiB
TeX
141 lines
5.2 KiB
TeX
\section{System Design}
|
|
|
|
|
|
\subsection{Architectural Principles}
|
|
|
|
Crowdstart adheres to four design principles:
|
|
|
|
\textbf{Principle 1: Stateless Request Handling.} No request depends on state from a previous request to the same instance. All state resides in managed services.
|
|
|
|
\textbf{Principle 2: Idempotent Operations.} All mutating operations can be safely retried. This enables automatic retry on transient failures without application-level deduplication logic.
|
|
|
|
\textbf{Principle 3: Eventual Consistency by Default.} We accept eventual consistency for most operations, reserving strong consistency for the critical path (inventory decrements, payment capture).
|
|
|
|
\textbf{Principle 4: Event-Driven Processing.} Long-running operations execute asynchronously via task queues. Synchronous request handlers complete within milliseconds.
|
|
|
|
\subsection{Core Components}
|
|
|
|
The Crowdstart architecture comprises five primary components:
|
|
|
|
\subsubsection{API Gateway}
|
|
|
|
The gateway handles authentication, rate limiting, and request routing. Implemented as a stateless Go service, it validates JWT tokens against a cached public key and routes requests to appropriate backend services.
|
|
|
|
\begin{lstlisting}[language=Go,caption=Request routing]
|
|
func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
ctx := appengine.NewContext(r)
|
|
|
|
// Authenticate
|
|
claims, err := g.auth.Validate(ctx, r)
|
|
if err != nil {
|
|
http.Error(w, "Unauthorized", 401)
|
|
return
|
|
}
|
|
|
|
// Route to backend
|
|
backend := g.router.Match(r.URL.Path)
|
|
backend.ServeHTTP(w, r.WithContext(
|
|
context.WithValue(ctx, "claims", claims),
|
|
))
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Product Catalog}
|
|
|
|
The catalog service manages product data: descriptions, pricing, images, and variants. Data resides in Google Cloud Datastore with Memcache providing read-through caching.
|
|
|
|
We model products as entities with multiple ancestor relationships:
|
|
|
|
\begin{lstlisting}[language=Go,caption=Product entity model]
|
|
type Product struct {
|
|
ID string `datastore:"-"`
|
|
StoreKey *datastore.Key `datastore:"-"`
|
|
Name string
|
|
Description string `datastore:",noindex"`
|
|
Price int64 // cents
|
|
Currency string
|
|
Variants []Variant `datastore:"-"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Ancestor queries enable strongly consistent reads within a store's product hierarchy, while cross-store queries accept eventual consistency.
|
|
|
|
\subsubsection{Inventory Management}
|
|
|
|
Inventory presents the primary consistency challenge. We implement a reservation-based model:
|
|
|
|
\begin{enumerate}
|
|
\item Customer adds item to cart $\rightarrow$ soft reservation (TTL: 15 minutes).
|
|
\item Checkout initiated $\rightarrow$ hard reservation (transaction).
|
|
\item Payment confirmed $\rightarrow$ inventory decrement.
|
|
\item Payment failed $\rightarrow$ reservation released.
|
|
\end{enumerate}
|
|
|
|
Hard reservations use Datastore transactions with optimistic concurrency:
|
|
|
|
\begin{lstlisting}[language=Go,caption=Inventory reservation]
|
|
func (s *InventoryService) Reserve(ctx context.Context, sku string, qty int) error {
|
|
key := datastore.NewKey(ctx, "Inventory", sku, 0, nil)
|
|
|
|
_, err := datastore.RunInTransaction(ctx, func(tc context.Context) error {
|
|
var inv Inventory
|
|
if err := datastore.Get(tc, key, &inv); err != nil {
|
|
return err
|
|
}
|
|
|
|
if inv.Available < qty {
|
|
return ErrInsufficientInventory
|
|
}
|
|
|
|
inv.Available -= qty
|
|
inv.Reserved += qty
|
|
_, err := datastore.Put(tc, key, &inv)
|
|
return err
|
|
}, nil)
|
|
|
|
return err
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Order Processing}
|
|
|
|
Orders transition through a state machine: \texttt{PENDING} $\rightarrow$ \texttt{PAID} $\rightarrow$ \texttt{FULFILLED} $\rightarrow$ \texttt{COMPLETED}. State transitions are driven by task queue handlers, ensuring durability and retry semantics.
|
|
|
|
\subsubsection{Payment Integration}
|
|
|
|
Payment processing integrates with Stripe via their Go library. We implement the payment flow as an idempotent operation using Stripe's idempotency keys:
|
|
|
|
\begin{lstlisting}[language=Go,caption=Idempotent payment capture]
|
|
func (p *PaymentService) Capture(ctx context.Context, order *Order) error {
|
|
params := &stripe.ChargeParams{
|
|
Amount: stripe.Int64(order.Total),
|
|
Currency: stripe.String(order.Currency),
|
|
Source: &stripe.SourceParams{Token: stripe.String(order.PaymentToken)},
|
|
}
|
|
params.SetIdempotencyKey(order.ID)
|
|
|
|
_, err := charge.New(params)
|
|
return err
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Data Model}
|
|
|
|
We employ a denormalized data model optimized for read-heavy workloads. Entity relationships are represented through key embedding rather than joins:
|
|
|
|
\begin{align}
|
|
\text{Store} &\rightarrow \text{Products} \rightarrow \text{Variants} \\
|
|
\text{Store} &\rightarrow \text{Orders} \rightarrow \text{LineItems} \\
|
|
\text{Customer} &\rightarrow \text{Addresses}, \text{PaymentMethods}
|
|
\end{align}
|
|
|
|
Denormalization introduces update anomalies. We mitigate this through:
|
|
\begin{itemize}
|
|
\item Immutable entities where possible (orders, transactions).
|
|
\item Background consistency jobs for derived data.
|
|
\item Version vectors for conflict resolution.
|
|
\end{itemize}
|
|
|