Live Demo: https://cf.macrometa.io/
Macrometa-lbrands Forever 42 App is a full-stack e-commerce web application that creates a storefront (and backend) for customers to shop for "fictitious" fashion items.
Originally based on the AWS bookstore template app (https://github.com/aws-samples/aws-bookstore-demo-app), this demo replaces all AWS services like below
- AWS DynamoDB,
- AWS Neptune (Graphs),
- AWS ElasticSearch (Search),
- AWS Lambda
- AWS Kinesis
This demo uses Macrometa's geo distributed data platform which provides a K/V store
, DynamoDB compatible document database
, graph database
, streams
and stream processing
along with Cloudflare edgeworkers
for the globally distributed functions as a service.
Unlike typical cloud platforms like AWS, where the backend stack runs in a single region, Macrometa and Cloudflare let you build stateful distributed microservices that run in 100s of regions around the world concurrently
. The application logic runs in cloudflare's low latency function as a service runtime on cloudflare PoPs and make stateful data requests to the closest Macrometa region. End to end latency for P90 is < 55ms
from almost everywhere in the world.
As a user of the demo, you can browse and search for fashion items, look at recommendations and best sellers, manage your cart, checkout, view your orders, and more.
Federation | Fabric | |
---|---|---|
Play | forever42 | demo@macrometa.io |
- Frontend is a Reactjs application which is hosted using Cloudflare.
- Web assets are stored on Cloudflare's KV store.
The core of backend infrastructure consists of Macrometa Document store(DB), Macrometa Edge store(DB), Macrometa Views(search), Macrometa Graphs and Cloudflare workers. Cloudflare workers issue C8QLs to talk with the GDN network.
The application leverages Macrometa GDN document store to store all the data for fashion items, orders, the checkout cart and users. When new purchases or new users are added the corresponding Macrometa Edge collection is also updated. These Edge collections along with Document collection acting as vertices are used by the Macrometa Graphs to generate recommendations for the users. When new purchases are added Macrometa Stream Workers also update the BestSellers Collection store in realtime from which the best sellers leaderboard is generated.
Catalog, Cart, Orders:
This is implemented using document collections
functionality in Macrometa GDN
Entity | Collection Name | Collection Type | Comment |
---|---|---|---|
Catalog | FashionItemsTable | document | Collection of the available fashion items. |
Cart | CartTable | document | Fashion items customers have addded in their cart. |
Orders | OrdersTable | document | Past orders of a customer. |
Promotions | PromotionsTable | document | Current fashion items being promoted as hot deals |
Store suggestions | ZipcodesTable | document | Forever 42 store based on given zipcode |
Recommendations:
This is implemented using graphs
functionality in Macrometa GDN. Each node in the graph is a vertex
and the links connecting the nodes are edges
. Both vertex
and edges
are document collections. The edges
require two additional mandatory indexes i.e., _from
and _to
.
Entity | Collection Name | Collection Type | Comment |
---|---|---|---|
Friends | friend | edge | Edge collection to capture friend relations. |
Purchase | purchased | edge | Edge collection to capture purchases. |
Users | UserTable | vertex | Document collection of available users. |
Catalog | FashionItemsTable | vertex | Collection of the available fashion items. |
Social | UserSocialGraph | graph | User social graph |
Search:
Search is implemented using views
functionality in Macrometa GDN. Search matches on the category
or the name
of fashion item in FashionItemsTable
with phrase matching.
Entity | Collection Name | Collection Type | Comment |
---|---|---|---|
Find | findFashionItems | view | The view which is queried for search. |
Indexes:
Create persistent indexes on the collection for the corresponding attributes
Collection | Attribute |
---|---|
CartTable | single index on customerId |
FashionItemsTable | N/A |
friend | N/A |
OrdersTable | customerId |
UsersTable | customerId , password |
PromotionsTable | N/A |
ZipcodesTable | N/A |
Below are the list of APIs being used.
Fashion Items (Macrometa Docuemnt Store DB)
- GET /api/fashionItems (ListFashionItem)
- GET /api/fashionItems/:id (GetFashionItem)
Store suggestions (Macormeta Document Store DB)
- GET /api/suggestion/:zipcode (GetLocationSuggestion)
Hot deals (Macrometa Document Store DB)
- GET /api/hot (GetHotDeals)
Cart (Macrometa Docuemnt Store DB)
- GET /api/cart (ListItemsInCart)
- POST /api/cart (AddToCart)
- PUT /api/cart (UpdateCart)
- DELETE /api/cart (RemoveFromCart)
Orders (Macrometa Docuemnt Store DB)
- GET /api/orders (ListOrders)
- POST /api/orders (Checkout)
Recommendations (Macrometa Graphs)
- GET /api/recommendations (GetRecommendations)
- GET /api/recommendations/:fashionItemId (GetRecommendationsByFashionItems)
Search (Macrometa Views)
- GET /api/search (Search)
C8QLs are used by the Cloudflare workers to communicate with Macrometa GDN.
signup:
INSERT {_key: @username, password: @passwordHash, customerId: @customerId} INTO UsersTable
signin:
FOR user in UsersTable FILTER user._key == @username AND user.password == @passwordHash RETURN user.customerId
AddFriends:
LET otherUsers = (FOR users in UsersTable FILTER users._key != @username RETURN users)
FOR user in otherUsers
INSERT { _from: CONCAT("UsersTable/",@username), _to: CONCAT("UsersTable/",user._key) } INTO friend
ListFashionItems:
FOR item IN FashionItemsTable FILTER item.category == @category RETURN item
GetFashionItem:
FOR item IN FashionItemsTable FILTER item.category == @category RETURN item
ListItemsInCart:
FOR item IN CartTable FILTER item.customerId == @customerId RETURN item
AddToCart:
UPSERT { _key: CONCAT_SEPARATOR(":", @customerId, @fashionItemId, @color, @size) }
INSERT { _key: CONCAT_SEPARATOR(":", @customerId, @fashionItemId, @color, @size),customerId: @customerId, fashionItemId: @fashionItemId, quantity: @quantity, price: @price, color: @color, size: @size }
UPDATE { quantity: @quantity } IN CartTable
UpdateCart:
FOR item IN CartTable UPDATE {_key: CONCAT_SEPARATOR(":", @customerId, @fashionItemId, @color, @size),quantity: @quantity} IN CartTable
RemoveFromCart:
REMOVE {_key: CONCAT_SEPARATOR(":", @customerId, @fashionItemId, @color, @size)} IN CartTable
ListOrders:
FOR item IN OrdersTable FILTER item.customerId == @customerId RETURN item
Checkout:
INSERT ${JSON.stringify(body)} INTO OrdersTable
LET items = (FOR item IN CartTable FILTER item.customerId == @customerId RETURN item)
FOR item IN items REMOVE item IN CartTable
AddPurchased:
LET order = first(FOR order in OrdersTable FILTER order._key == @orderId RETURN {customerId: order.customerId, fashionItems: order.fashionItems})
LET customerId = order.customerId
LET userId = first(FOR user IN UsersTable FILTER user.customerId == customerId RETURN user._id)
LET fashionItems = order.fashionItems
FOR fashionItem IN fashionItems
INSERT {_from: userId, _to: CONCAT("FashionItemsTable/",fashionItem.fashionItemId)} INTO purchased
GetRecommendations:
LET userId = first(FOR user in UsersTable FILTER user.customerId == @customerId return user._id)
FOR user IN 0..1 ANY userId friend
FOR fashionItem IN 0..1 OUTBOUND user purchased
LIMIT 3
RETURN DISTINCT fashionItem._key
GetRecommendationsByFashionItems:
LET userId = first(FOR user in UsersTable FILTER user.customerId == @customerId return user._id)
LET fashionItemId = CONCAT("FashionItemsTable/",@fashionItemId)
FOR friendsPurchased IN 0..1 INBOUND fashionItemId purchased
FOR user IN 0..1 ANY userId friend
FILTER user._key == friendsPurchased._key
LIMIT 3
RETURN DISTINCT user
Search
FOR doc IN findFashionItems
SEARCH PHRASE(doc.heading, @search, "text_en") OR PHRASE(doc.category, @search, "text_en")
SORT BM25(doc) desc
RETURN doc
GetLocationSuggestion
FOR doc IN ZipcodesTable FILTER doc._key == @key RETURN doc
GetHotDeals
FOR item IN PromotionsTable RETURN item
Search functionality is powered by Macrometa Views. This is saved as findFashionItems
with below config:
{
"links": {
"FashionItemsTable": {
"analyzers": ["identity"],
"fields": {
"heading": {
"analyzers": ["text_en"]
},
"category": {
"analyzers": ["text_en"]
}
},
"includeAllFields": false,
"storeValues": "none",
"trackListPositions": false
}
},
"name": "findFashionItems",
"primarySort": []
}
- Fashion item images are stored in Cloudflare KV under
LBRANDS_IMAGES
. frontend
folder contains the code for the UI which is in Reactjsbackend
folder contains the backend part. This is responsible for making calls to Macrometa GDN.- Calls with
/api/
are treated as calls which want to communicate with Macrometa GDN, others are understood to be calls for the web assets. backend/c8qls.js
contains the queries (C8QL). These are executed by calling Macrometa GDN/cursor
API. Thebind variables
in the body of the request are the parameters to the queries.
There are multiple ways to install the workers CLI. Official docs say it to install via npm or cargo.
Additionally the binary can also be installed manually. Details of which can be found here under the Manual Install
section - I personally have the binaries.
It is advisable to have npm
installed via nvm
to avoid getting into issues when installing global packages. Additional details can be found in their github repo.
We will need the Macrometa API token to be able to configure the CLI. Please signup for a macrometa account for the token, or create your own by following the docs if you already have an account here
Run wrangler config
and enter the above API token when asked for. More details can be found here
wrangler.toml
already has the configurations.
Provide a
C8_API_KEY
with a correct API key before proceeding.
vars
provides the environment variable we use in the workers itself. They include:
C8_URL
: GDN federation URLC8_FABRIC
: GDN fabric nameC8_API_KEY
: API key of the tenant being used
Make sure to run npm i
on the project's root to install the necessary dependencies.
If there are changes to the UI code then first run npm run build
to make the UI build, else you can directly proceed with publishing.
Run wrangler publish
and it will deploy your worker along with the static files used by the UI.
Once the worker is deployed, execute the following curl:
curl -X POST 'https://fashionstore.macrometadev.workers.dev/api/setup'
This will create all the collections and dummy data for you.
Note: This will only populate if the collection or stream app is not already present. If it does it wont create the dummy data, even if the collection is empty. So best to delete the collection if you want it to be populated by the curl.
- Now login to the tenant and activate the stream app.
- Edit and save the view with the correct data if not initialised properly. Details can be found in
init.js