-
Notifications
You must be signed in to change notification settings - Fork 3
Phoenix microservices
This document is intended as a tech spec of phoenix microservices split out of phoenix and hopefully a dev guide for migrating or creating new ones.
There are several key points to splitting phoenix:
- API contract between phoenix and microservice acts as spec and documentation
- we can test (and performance test!) pieces individually
- switch to new microservice version can be performed only when we're sure everything works as expected
- implementation can be chosen dynamically for every tenant (allowing us to have different independent implementations of varying complexity, and even allowing customers to have their own implementations against the given API contract)
- less code to build, transfer over network and deploy
- less accidentally tangled code in services
Phoenix becomes an orchestration layer
- performing calls to microservices
- sequentially or in parallel
- caching results
- picking appropriate implementation for the tenant who initiated request
- composing response entities and failures from the collected data
- making no database queries
- providing a unified API and proxying requests to all microservices
- pushing data to kafka directly
- shared code items:
- starfish, framework, generic response structures (responses with warnings/errors etc)
- payloads and responses
- phoenix must have no notion of microservice's models and tables for no "accidental" queries
- microservice should have its own database/schema (TODO: figure the exact design here) and migrations using this approach
- microservice URL must be configured via
application.conf
in phoenix - naming format for microservice:
phoenix-$subsystem
where$subsystem
is something likepromotions
- technology stack may differ from the one used in phoenix
- we want to replace
json4s
withcirce
- we want to replace
akka-http
with something else (TODO: make decision) - otherwise, depending on requirements and circumstances, we can decide to change the stack, but this decision must be carefully discussed
- we want to replace
- project dependencies and versions must be defined in phoenix
project/
and reused by microservices
- payloads and responses serve as an API contract between phoenix and microservices
- if payload or response is changed on one side (e.g. microservice), compilation must fail at another (e.g. phoenix)
- payloads and responses must be classified as
- internal (phoenix ↔ microservices)
- external (client ↔ phoenix)
Internal API can assume that a "communication context" exists, and does not need to be detailed. If a request fails, a suggested response format would be a well-formed data for phoenix to render error upon and a debug message. On the example of MWH /reservations/hold
endpoint, given phoenix has sent a request to hold SKUs A, B and C:
This endpoint does one thing, and irregardless of why the request can't be processed for SKUs A and B, phoenix will render "Sorry, SKUs A and B are out of stock". So phoenix only needs to know that A and B were offending. For debugging purposes, a debug
section is attached to MWH response, describing that SKU A was out of stock but SKU B for some reason had no corresponding entry in database. Sample error response JSON:
{
"errors": [
{ "sku": "A", "debug": "out of stock" },
{ "sku": "B", "debug": "no entry found in table foo_bar for foo_id=124" }
]
}
For debugging purposes, communication protocol must be extended with at least requestId
field to trace the request. This section is to be filled in later.
Phoenix returns either data or error to render on UI. Client has less context of possible failures when sending requests to phoenix, hence phoenix must provide more detail in case of failure. For now, this means rendering an error message string based on data returned from internal queries and/or microservice response. For the above example of MWH hold failure, phoenix must collect offending SKU codes from MWH response and render a string like "Impossible to complete checkout because some items are out of stock. Please remove SKUs A and B to proceed". Better phoenix error API is currently under development.
- API contract design
- microservice implementation
- microservice testing (preferably including performance testing)
- phoenix refactoring to use the new microservice
- in case of rewriting microservice, change in config to point to new impl
Promotion is "composed" of qualifier and offer. We need to check is some cart metadata qualifies against any promotions, and if so, compare promotions' offers against current total and return the best one.
Copying things over from phoenix impl, here's the current spec:
Qualifiers:
- any (no checks)
- number of items
- total cost of items
- total cost of cart
- customer dynamic group
Offers:
- percent off one item
- percent off multiple items
- amount off one item
- amount off multiple items
- free shipping
Fetch, create, update and archive endpoints must be carried over from phoenix as is.
POST /v1/promotions/auto-apply
Returns 200 OK
and the best (that will shave off the biggest sum off cart total) auto-apply promotion for given cart metadata.
Returns 204 NoContent
if there isn't an applicable auto-apply promo.
phoenix → phoenix-promotions
Cart metadata
customerGroupIds: Option[NEL[Int]] // for DCG-based promos
appliedCouponId(s): Option[NEL[Int]] // for (non-)exclusive promos
cartTotal: Int // does not equal to sum of line item totals, also includes shipping cost and possibly some other unanticipated stuff
shippingCost: Int // to estimate if free shipping offer is beneficial
lineItems: NEL[
skuId: Int // for offers based on items
type: Regular | GiftCard // promo can omit GCs in qualification
quantity: Int // for offers based on item qty
unitPrice: Int
]
Request should contain all data for phoenix-promotions
to run all qualifier checks and decide on promotion. Putting this note in case description is missing something.
To support the idea of API acting as spec and contract: looking at this payload, one can assume that all the data passed there makes sense for qualifier judgement.
phoenix-promotions → phoenix
Promotion details
id: Int
name: String
customAttrs: Json // maybe
offer: PercentOff(skuIds: [Int], percentOff: Int) | AmountOff(skuIds: [Int], amountOff: Int) | FreeShipping
TBD