Skip to content

Conversation

@raymondfeng
Copy link
Contributor

The PR adds an addItem api to atomically add items to a cart.

The implementation utilizes execute, which is a bit hacky.

async addItem(userId: string, item: ShoppingCartItem) {
const connector = this.kvModelClass.dataSource!.connector!;
// tslint:disable-next-line:no-any
const execute = promisify((cmd: string, args: any[], cb: Function) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow what execute does? Apart for this and it's usage, the rest of it looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execute basically runs a Redis command.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we're still using this.get, this.set? So what do the commands do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.get/this.set delegate to execute behind the scene.

Copy link
Contributor Author

@raymondfeng raymondfeng Sep 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason to use execute is that we don't have a this.checkAndSet. Maybe we should add it to the KVRepository, such as:

checkAndSet(key, current => {... return newValue; });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read up some more on Redis commands and transactions at https://redis.io/commands

Makes sense, I can see this being a common pattern so a helper function for transaction would definitely be a good idea. That said I'm not sure why this needs to be a transaction as there is only one item (shopping cart). We're not creating items themselves as entries in Redis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm considering the multiple device case. A user can access the app from multiple devices that share the same cart.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But each call does a get and then set. Is a user really going to generate multiple requests at the same time from multiple devices?

Either way, I'm ok with the change -- can you just add a comment stating the reason for using a transaction so it's clear to anyone looking at this example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more comments.

@property()
price?: number;

constructor(data?: Partial<ShoppingCartItem>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the partial needed in this case since this isn't a model directly exposed over REST and won't face issues of trying to create an instance without a productId?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal as the data itself is optional. So we can create a empty instance and populate the data afterward. The required constraint should be enforced by model validation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required constraint should be enforced by model validation.

@raymondfeng Please note that KeyValue repository does not perform validation right now! See lib/kvao/set.js. I think this is an oversight we should eventually fix.

@raymondfeng raymondfeng merged commit ec06800 into master Sep 19, 2018
@raymondfeng raymondfeng deleted the add-cart-item branch September 19, 2018 16:21

/**
* Use Redis WATCH and Transaction to check and set against a key
* See https://redis.io/topics/transactions#optimistic-locking-using-check-and-set
Copy link
Member

@bajtos bajtos Sep 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raymondfeng IIUC, the algorithm outlined in the linked page is expecting to re-run the transaction if a race condition was detected, and repeat those re-run until the transaction succeed.

Using the above code, if there are race conditions and another client modifies the result of val in the time between our call to WATCH and our call to EXEC, the transaction will fail.
We just have to repeat the operation hoping this time we'll not get a new race. This form of locking is called optimistic locking and is a very powerful form of locking. In many use cases, multiple clients will be accessing different keys, so collisions are unlikely – usually there's no need to repeat the operation.

I don't see that logic implemented here. Are we expecting the REST clients to retry the operation? Could you please explain how are we signaling the race condition to them?

userId: 'user-0001',
items: [
{
new ShoppingCartItem({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use givenAnItem() please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants