Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

[WIP] Async sendMoney #17

Closed
wants to merge 10 commits into from
Closed

[WIP] Async sendMoney #17

wants to merge 10 commits into from

Conversation

nkramer44
Copy link
Contributor

addresses #15 and #16

This PR converts sendMoney from a blocking call to an async call. A request to sendMoney will now return almost immediately without waiting for the payment to complete. Instead of returning a SendPaymentResult or a GRPC analogue, Hermes will register the payment with a PENDING status and return a Payment object immediately. Users can then poll the getPayment endpoint to see the status of the Payment.

This doesn't solve all of our resiliency issues, but does solve for the situation in which the client disconnects from Hermes. We could have done idempotent sendMoney without async, but async seems to provide a better user experience than blocking during a potentially long ILP payment.

Typical flow would look like:

sendMoney:

curl --location --request PUT 'http://{hermes-host}/accounts/{accountId}/payments/{UUID paymentId}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {auth_token}' \
--data-raw '{
	"amount" : {sendAmount},
	"destinationPaymentPointer": "{receiverPaymentPointer}"
}'

returns:

{
    "paymentId": "{paymentId}",
    "senderAccountId": "{senderAccountId}",
    "originalAmount": "{sendAmount}",
    "amountDelivered": "0",
    "amountSent": "0",
    "amountLeftToSend": "{sendAmount}",
    "destination": "{receiverPaymentPointer}",
    "status": "PENDING"
}

getPayment:

curl --location --request GET 'http://{hermes_host}/payments/{paymentId}'

returns:

{
    "paymentId": "{paymentId}",
    "senderAccountId": "{senderUserId}",
    "originalAmount": "{sendAmount}",
    "amountDelivered": "{amount delivered by stream sender}",
    "amountSent": "{amount sent by stream sender}",
    "amountLeftToSend": "{amount left to send by stream sender}",
    "destination": "{receiverPaymentPointer}",
    "status": "{SUCCESSFUL or FAILED}"
}

NOTE: PENDING and SUCCESSFUL/FAILED payments are stored in Redis, but these entries are only updated with once a STREAM payment has completed or an error occurred in STREAM sender, and thus are not real-time figures.

Signed-off-by: nkramer44 <noah.ph.kramer@gmail.com>
Signed-off-by: nkramer44 <noah.ph.kramer@gmail.com>
Signed-off-by: nkramer44 <noah.ph.kramer@gmail.com>
Signed-off-by: nkramer44 <noah.ph.kramer@gmail.com>
Copy link
Contributor

@theotherian theotherian left a comment

Choose a reason for hiding this comment

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

Looking very good thus far. Added a few comments regarding some design decisions we should discuss.

message GetPaymentResponse {

// Unique ID (UUID) of the payment
string payment_id = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

I've been thinking about this, and part of me is thinking that both SendMoney and GetPayment (along with their REST counterparts) should probably return the same response.

I'm trying to think if there's anything that would be different in a way between the two responses that would necessitate having them be separate entities.

My rationale for this is that it makes it a little easier on the client side if you want to have a blocking vs non-blocking wrapper for sending a payment if the response types are the same, because then the interface can be common for both implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. Having two protobufs that are identical makes me uncomfortable. We should check on this, but in our original meeting with Keefer to discuss GRPC, I think he said that best practice is to have a different protobuf for each request/response, even if they are identical.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think he said that best practice is to have a different protobuf for each request/response, even if they are identical.

😕

I'm not sure I understand why that's considered a best practice. I'm fine with pushing back on that one, especially considering that it likely makes his life harder when it comes to the new behavior.

I'm also curious to see what the GRPC best practices are and why they're best practices because this is at least the second one I've heard of that left me scratching my head.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, we should do some research on this. I just remember that in my original protobufs, I returned a protobuf called AccountSettings that mirrored the connector AccountSettings for both createAccount and getAccount, but I was told to separate them....

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 would love to get rid of the redundant protobufs... maybe I could actually reuse some code if we did that

@@ -7,17 +7,29 @@ package org.interledger.stream.proto;
// Next field: 4
message SendPaymentResponse {
Copy link
Contributor

Choose a reason for hiding this comment

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

Per my previous comment, I'm wondering if this and GetPaymentResponse can be the same.

@@ -233,12 +234,29 @@ public static AccountSettings accountSettingsFromCreateAccountRequest(String aut
return customSettings;
}

public static SendPaymentResponse sendPaymentResponseFromSendMoneyResult(SendMoneyResult result) {
public static SendPaymentResponse sendPaymentResponseFromPayment(Payment result) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Per the previous comments, these should probably end up being the same type.

.orElseThrow(() -> new AccountNotFoundProblem(senderAccountId));

final InterledgerAddress senderAddress =
InterledgerAddress.of("test.jc.money").with(senderAccountId.value());
Copy link
Contributor

Choose a reason for hiding this comment

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

This value obviously shouldn't be hard coded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also agree. This should be the address of the connector, right?

If so, we can get that from the Metadata api on the connector.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would assume that's correct.

@nkramer44 nkramer44 closed this Mar 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants