Skip to content

Commit

Permalink
Executable example with multiple HystrixCommands
Browse files Browse the repository at this point in the history
  • Loading branch information
benjchristensen committed Nov 30, 2012
1 parent 0da4f5b commit b989804
Show file tree
Hide file tree
Showing 10 changed files with 846 additions and 0 deletions.
5 changes: 5 additions & 0 deletions hystrix-examples/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
apply plugin: 'java'
dependencies {
compile project(':hystrix-core')
}

task(runDemo, dependsOn: 'classes', type: JavaExec) {
main = 'com.netflix.hystrix.examples.demo.HystrixCommandDemo'
classpath = sourceSets.main.runtimeClasspath
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright 2012 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.hystrix.examples.demo;

/**
* POJO for holding the result of a CreditCardAuthorization
*/
public class CreditCardAuthorizationResult {

public static CreditCardAuthorizationResult createSuccessResponse(String transactionID, String authorizationCode) {
return new CreditCardAuthorizationResult(true, transactionID, authorizationCode, false);
}

public static CreditCardAuthorizationResult createDuplicateSuccessResponse(String transactionID, String authorizationCode) {
return new CreditCardAuthorizationResult(true, transactionID, authorizationCode, true);
}

public static CreditCardAuthorizationResult createFailedResponse(String message) {
return new CreditCardAuthorizationResult(false, message, null, false);
}

private final boolean success;
private final boolean isDuplicate;
private final String authorizationCode;
private final String transactionID;
private final String errorMessage;

/**
* Private constructor that normally would be a horrible API as it re-uses different arguments for different state.
*
* @param success
* @param value
* @param isResponseDuplicate
* boolean whether the response is the result of a duplicate transaction returning a previously submitted transaction result
* <p>
* This is for handling the idempotent double-posting scenario, such as retries after timeouts.
*/
private CreditCardAuthorizationResult(boolean success, String value, String value2, boolean isResponseDuplicate) {
this.success = success;
this.isDuplicate = isResponseDuplicate;
if (success) {
this.transactionID = value;
this.authorizationCode = value2;
this.errorMessage = null;
} else {
this.transactionID = null;
this.errorMessage = value;
this.authorizationCode = null;
}
}

public boolean isSuccess() {
return success;
}

/**
* Whether this result was a duplicate transaction.
*
* @return boolean
*/
public boolean isDuplicateTransaction() {
return isDuplicate;
}

/**
* If <code>isSuccess() == true</code> this will return the authorization code.
* <p>
* If <code>isSuccess() == false</code> this will return NULL.
*
* @return
*/
public String getAuthorizationCode() {
return authorizationCode;
}

/**
* If <code>isSuccess() == true</code> this will return the transaction ID.
* <p>
* If <code>isSuccess() == false</code> this will return NULL.
*
* @return
*/
public String getTransactionID() {
return transactionID;
}

/**
* If <code>isSuccess() == false</code> this will return the error message.
* <p>
* If <code>isSuccess() == true</code> this will return NULL.
*
* @return
*/
public String getErrorMessage() {
return errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/**
* Copyright 2012 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.hystrix.examples.demo;

import java.math.BigDecimal;
import java.net.HttpCookie;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;

/**
* This class was originally taken from a functional example using the Authorize.net API
* but was modified for this example to use mock classes so that the real API does not need
* to be depended upon and so that a backend account with Authorize.net is not needed.
*/
// import net.authorize.Environment;
// import net.authorize.TransactionType;
// import net.authorize.aim.Result;
// import net.authorize.aim.Transaction;

/**
* HystrixCommand for submitting credit card payments.
* <p>
* No fallback implemented as a credit card failure must result in an error as no logical fallback exists.
* <p>
* This implementation originated from a functional HystrixCommand wrapper around an Authorize.net API.
* <p>
* The original used the Authorize.net 'duplicate window' setting to ensure an Order could be submitted multiple times
* and it would behave idempotently so that it would not result in duplicate transactions and each would return a successful
* response as if it was the first-and-only execution.
* <p>
* This idempotence (within the duplicate window time frame set to multiple hours) allows for clients that
* experience timeouts and failures to confidently retry the credit card transaction without fear of duplicate
* credit card charges.
* <p>
* This in turn allows the HystrixCommand to be configured for reasonable timeouts and isolation rather than
* letting it go 10+ seconds hoping for success when latency occurs.
* <p>
* In this example, the timeout is set to 3,000ms as normal behavior typically saw a credit card transaction taking around 1300ms
* and in this case it's better to wait longer and try to succeed as the result is a user error.
* <p>
* We do not want to wait the 10,000-20,000ms that Authorize.net can default to as that would allow severe resource
* saturation under high volume traffic when latency spikes.
*/
public class CreditCardCommand extends HystrixCommand<CreditCardAuthorizationResult> {
private final static AuthorizeNetGateway DEFAULT_GATEWAY = new AuthorizeNetGateway();

private final AuthorizeNetGateway gateway;
private final Order order;
private final PaymentInformation payment;
private final BigDecimal amount;

/**
* A HystrixCommand implementation accepts arguments into the constructor which are then accessible
* to the <code>run()</code> method when it executes.
*
* @param order
* @param payment
* @param amount
*/
public CreditCardCommand(Order order, PaymentInformation payment, BigDecimal amount) {
this(DEFAULT_GATEWAY, order, payment, amount);
}

private CreditCardCommand(AuthorizeNetGateway gateway, Order order, PaymentInformation payment, BigDecimal amount) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CreditCard"))
// defaulting to a fairly long timeout value because failing a credit card transaction is a bad user experience and 'costly' to re-attempt
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationThreadTimeoutInMilliseconds(3000)));
this.gateway = gateway;
this.order = order;
this.payment = payment;
this.amount = amount;
}

/**
* Actual work of submitting the credit card authorization occurs within this <code>HystrixCommand.run()</code> method.
*/
@Override
protected CreditCardAuthorizationResult run() {
// Simulate transitive dependency from CreditCardCommand to GetUserAccountCommand.
// UserAccount could be injected into this command as an argument (and that would be more accurate)
// but often in large codebase that ends up not happening and each library fetches common data
// such as user information directly such as this example.
UserAccount user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute();
if (user.getAccountType() == 1) {
// do something
} else {
// do something else
}

// perform credit card transaction
Result<Transaction> result = gateway.submit(payment.getCreditCardNumber(),
String.valueOf(payment.getExpirationMonth()),
String.valueOf(payment.getExpirationYear()),
TransactionType.AUTH_CAPTURE, amount, order);

if (result.isApproved()) {
return CreditCardAuthorizationResult.createSuccessResponse(result.getTarget().getTransactionId(), result.getTarget().getAuthorizationCode());
} else if (result.isDeclined()) {
return CreditCardAuthorizationResult.createFailedResponse(result.getReasonResponseCode() + " : " + result.getResponseText());
} else {
// check for duplicate transaction
if (result.getReasonResponseCode().getResponseReasonCode() == 11) {
if (result.getTarget().getAuthorizationCode() != null) {
// We will treat this as a success as this is telling us we have a successful authorization code
// just that we attempted to re-post it again during the 'duplicateWindow' time period.
// This is part of the idempotent behavior we require so that we can safely timeout and/or fail and allow
// client applications to re-attempt submitting a credit card transaction for the same order again.
// In those cases if the client saw a failure but the transaction actually succeeded, this will capture the
// duplicate response and behave to the client as a success.
return CreditCardAuthorizationResult.createDuplicateSuccessResponse(result.getTarget().getTransactionId(), result.getTarget().getAuthorizationCode());
}
}
// handle all other errors
return CreditCardAuthorizationResult.createFailedResponse(result.getReasonResponseCode() + " : " + result.getResponseText());
/**
* NOTE that in this use case we do not throw an exception for an "error" as this type of error from the service is not a system error,
* but a legitimate usage problem successfully delivered back from the service.
*
* Unexpected errors will be allowed to throw RuntimeExceptions.
*
* The HystrixBadRequestException could potentially be used here, but with such a complex set of errors and reason codes
* it was chosen to stick with the response object approach rather than using an exception.
*/
}
}

/*
* The following inner classes are all mocks based on the Authorize.net API that this class originally used.
*
* They are statically mocked in this example to demonstrate how Hystrix might behave when wrapping this type of call.
*/

public static class AuthorizeNetGateway {
public AuthorizeNetGateway() {

}

public Result<Transaction> submit(String creditCardNumber, String expirationMonth, String expirationYear, TransactionType authCapture, BigDecimal amount, Order order) {
/* simulate varying length of time 800-1500ms which is typical for a credit card transaction */
try {
Thread.sleep((int) (Math.random() * 700) + 800);
} catch (InterruptedException e) {
// do nothing
}

/* and every once in a while we'll cause it to go longer than 3000ms which will cause the command to timeout */
if (Math.random() > 0.99) {
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
// do nothing
}
}

if (Math.random() < 0.8) {
return new Result<Transaction>(true);
} else {
return new Result<Transaction>(false);
}

}
}

public static class Result<T> {

private final boolean approved;

public Result(boolean approved) {
this.approved = approved;
}

public boolean isApproved() {
return approved;
}

public ResponseCode getResponseText() {
return null;
}

public Target getTarget() {
return new Target();
}

public ResponseCode getReasonResponseCode() {
return new ResponseCode();
}

public boolean isDeclined() {
return !approved;
}

}

public static class ResponseCode {

public int getResponseReasonCode() {
return 0;
}

}

public static class Target {

public String getTransactionId() {
return "transactionId";
}

public String getAuthorizationCode() {
return "authorizedCode";
}

}

public static class Transaction {

}

public static enum TransactionType {
AUTH_CAPTURE
}

}
Loading

0 comments on commit b989804

Please sign in to comment.