-Real time inventory updates
-Automated stock alerts
-Integration w/sales channels to optimize inventory control
-Interface for inventory tracking, stock replenishment, order fulfillments
-User roles and permissions
-Reporting/analytics
-Mobile app
-Supplier management to streamline replenishment process
-Barcode scanning
-Multi location support
-Manage inventory over multiple locations/stores and real time syncing
-Returns management (track/manage product returns)
-Integration w/accounting system
-Must be highly scalable
-Data must be highly consistent
-Real time data tracking means this data must be highly available
-User credentials must be securely stored
-Stateless storage of user credentials
Assuming a user can start off with a cap of 50 associated stores/inventories, around 10 million inventory users worldwide which we will support 10% of to start off (1 million users), and this system has a life span of 10 years, also each store can have a max of 10,000 items
estimated space:
100k bytes + 40 + 30 + 10+ 1000 + 1000 + 1000 + 100k + 1000 + 100 = 204k bytes per store/year * 50 stores per user *1 million users * 10 years = 1.02e+14 bytes ie 102 TB
100 bytes
User{
id: 10 bytes,
name: 10 bytes,
role: 10 bytes,
password: 10 bytes,
inventories: 500 bytes,
email: 10 bytes,
}
1000 bytes
Brand{
id: 10 bytes,
inventoryIds: 500 bytes,
admins: 100 bytes
}
100,000 bytes
Inventory{
id: 10 bytes,
productIds: 100,000 bytes,
salesChannels: 1000 bytes,
location: 10 bytes,
brand: 10 bytes,
name: 10 bytes,
refund_policy_time: 10 bytes,
}
1000 bytes
Product{
productId: 10 bytes,
productName: 10 bytes,
productBrand: 10 bytes,
productDescription: 10 bytes,
productWeight: 10 bytes,
productSize: 10 bytes,
productType: 10 bytes,
expiratonDate: 10 bytes,
inventoryId: 10 bytes,
quantity: 10 bytes,
refillQuantity: 10 bytes,
threshold: 10 bytes,
}
1000 bytes
ArchivedProduct{
productId: 10 bytes,
productName: 10 bytes,
productBrand: 10 bytes,
productDescription: 10 bytes,
productWeight: 10 bytes,
productSize: 10 bytes,
productType: 10 bytes,
expiratonDate: 10 bytes,
inventoryId: 10 bytes,
quantity: 10 bytes,
threshold: 10 bytes,
}
1000 bytes
Return{
id: 10 bytes,
orderId: 10 bytes,
date: 10 bytes,
productIds: 1000 bytes,
customerId: 10 bytes,
customerEmail: 10 bytes,
reason: 100 bytes,
condition: 10 bytes,
returnMethod: 10 bytes,
isExchange: 10 bytes,
isRefund: 10 bytes
}
10 bytes
Scan{
productId: 10 bytes
}
30 bytes
SalesChannel{
id: 10 bytes,
salesChannelId: 10 bytes,
inventoryId: 10 bytes
}
40 bytes
SalesChannelIntegration{
id: 10 bytes,
name: 10 bytes,
storeId: 10 bytes,
inventoryId: 10 bytes
}
100000 bytes
For allowing a supplier user to update inventory (can update a max 100 products per supply update for now)
SupplyList{
id: 10 bytes,
inventory_id: 10 bytes,
Supplies: 100,000 bytes,
date: 10 bytes
}
CreateUser(username,password,email,role) -> Create a new user
Login(username,password) -> generate user login token
RefreshToken(username) -> upon user activity, check if a user's token is about to expire and then reset the token's expiry timer/generate a new token for the user (depending on how the login service works)
Notification(email_id, inventory_id, message) -> Notifies through both the app and email for whatever reason determined by the application
AddInventory(location,name,userId,brand) -> After checking userId is an admin under a certain brand, create a new inventory under the brand name with its own custom name (optional)
AddSalesChannel(inventory_id,sales_channel_id,user_id) -> Allows an admin to allow verified sales channels (brick and mortar stores, shopify stores, etc) to update their inventory
CreateBrand(userId, Brand) -> Create a new brand and assign the user who created it as its first admin
AddProduct(quantity,name,description,productBrand,weight,size,type,inventory_id) -> Add a new product under a pre existing inventory
RemoveProduct(productId) -> Remove a product that is listed under a store
ArchiveProduct(productId) -> Save a product and its details, even if its removed
ViewInventory(inventory_id,user_id,salesChannelId,brand_id) -> allows users associated with the inventory or saleschannel vendors to view stock of a certain store or total stock of a brand itself
UpdateQuantity(productId,user_id,salesChannelId) -> If logged in and have credentials as admin or manager, then update the quantity of a product manually, also gives an optional parameter of salesChannelId which lets stores access to using this function to update quantity. Also if product inventory falls below threshold, send notification email to inventory admin/managers. Create SalesInfo object to record product metrics.
SupplierInventoryUpdate(inventory_id,product_id) -> Checks if supplier is a verified user under a inventory and allows them to update company inventory after the company buys product from them. (uses addproduct function) Will throw an error if the inventory is full for a certain item
Error(message) -> Visual error thrown if an error is encountered
ReturnItem(orderId,inventory_id,user_id,customerId,reason,condition,returnMethod,isExchange,isRefund,productIds,customerEmail) -> allows certain users associated with an inventory to issue refunds, can check date with refund policy time of an inventory to deny refunds due to the company's refund policy
SendEmail(destination_email_id,source_email_id,body) -> Send an email to a user/customer for various reasons (successful order, payment issue, stock issues,etc)
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
User{
id: string,
name: string,
role: string,
password: string,
inventories: string[],
email: string,
}
Brand{
id: string,
inventoryIds: string[],
admins: string[]
}
Inventory{
id: string,
productIds: string[],
salesChannels: SalesChannel[],
location: string,
brand: string,
name: string,
refund_policy_time: int (in hours),
}
Product{
productId: string,
productName: string,
productBrand: string,
productDescription: string,
productWeight: float,
productSize: float (volume),
productType: string,
expiratonDate: datetime,
inventoryId: string,
quantity: int,
refillQuantity: int,
threshold: int,
}
SalesInfo{
id:string,
date:datetime,
products:SalesProduct[],
}
SalesProduct{
id: string,
productId: string,
quantity: string
}
ArchivedProduct{
productId: string,
productName: string,
productBrand: string,
productDescription: string,
productWeight: float,
productSize: float (volume),
productType: string,
expiratonDate: datetime,
inventoryId: string,
quantity: int,
threshold: int,
}
Return{
id: string,
orderId: string,
date: datetime,
productIds: string[],
customerId: string,
customerEmail: string,
reason: string,
condition: string,
returnMethod: string,
isExchange: bool,
isRefund: bool
}
Scan{
productId: string
}
SalesChannel{
id: string,
salesChannelId: string,
inventoryId: string
}
SalesChannelIntegration{
id: string,
name: string,
storeId: string,
}
For allowing a supplier user to update inventory (can update a max 100 products per supply update for now)
SupplyList{
id: string,
inventory_id: string,
Supplies: Product[],
date: datetime
}
LowInventoryItems{
id: string,
inventory_id:String,
items: Product[],
date: datetime
}
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
UpdateQuantity(productId,user_id,salesChannelId) -> If logged in and have credentials as admin or manager, then update the quantity of a product manually, also gives an optional parameter of salesChannelId which lets stores access to using this function to update quantity. Also if product inventory falls below threshold, send notification email to inventory admin/managers. Checks the product's low inventory threshold limit, once the quantity has been updated below this threshold, it will send a resupply request to restock the product for the difference. The cache policy will ensure the best number of items refilled is chosen if the store is experiencing high traffic and continuously decreasing quantities.
AddSalesChannel(inventory_id,sales_channel_id,user_id) -> Allows an admin to allow verified sales channels (brick and mortar stores, shopify stores, etc) to update their inventory. Utilizes SalesChannel and SalesChannelIntegration tables. The saleschannelintegration table takes information from a vendor such as store name, shopifyid, etc and the Saleschannel table normalizes this data with the appropriate inventory id, this must be done by a admin/manager of an inventory.
-Normalizing the saleschannel table was important because I forsee this information being very read heavy, so instead of querying two tables, you can just query the sales channel table to find it's associated inventory id
-I opt for a relational database since this data needs to be highly consistent and I don't foresee it changing much except for potential changes to the product quantities.
-Opt for lazy loading caching strategy since some stores that are very popular may experience high traffic (exclusive sale, highly sought after item, etc). We can update the product inventory in the cache instead of repeatedly querying the database and change product quantities upon calmer traffic
-Network issues between app server and database
-Latency
-Race conditions
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?
-Implement retry mechanisms, failover strategies etc
-Optimize network, use cdns for static
-Database lock using optimistic concurrency control