From b98980493f53f8669c3680fcaf4c8f6ce1053fb2 Mon Sep 17 00:00:00 2001 From: Ben Christensen Date: Thu, 29 Nov 2012 22:40:57 -0800 Subject: [PATCH] Executable example with multiple HystrixCommands --- hystrix-examples/build.gradle | 5 + .../demo/CreditCardAuthorizationResult.java | 110 ++++++++ .../examples/demo/CreditCardCommand.java | 237 ++++++++++++++++++ .../examples/demo/GetOrderCommand.java | 63 +++++ .../demo/GetPaymentInformationCommand.java | 63 +++++ .../examples/demo/GetUserAccountCommand.java | 131 ++++++++++ .../examples/demo/HystrixCommandDemo.java | 92 +++++++ .../netflix/hystrix/examples/demo/Order.java | 35 +++ .../examples/demo/PaymentInformation.java | 47 ++++ .../hystrix/examples/demo/UserAccount.java | 63 +++++ 10 files changed, 846 insertions(+) create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java create mode 100644 hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java diff --git a/hystrix-examples/build.gradle b/hystrix-examples/build.gradle index 1b4cdba6b..a91edb47d 100644 --- a/hystrix-examples/build.gradle +++ b/hystrix-examples/build.gradle @@ -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 } \ No newline at end of file diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java new file mode 100644 index 000000000..3f00f71ac --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardAuthorizationResult.java @@ -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 + *

+ * 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 isSuccess() == true this will return the authorization code. + *

+ * If isSuccess() == false this will return NULL. + * + * @return + */ + public String getAuthorizationCode() { + return authorizationCode; + } + + /** + * If isSuccess() == true this will return the transaction ID. + *

+ * If isSuccess() == false this will return NULL. + * + * @return + */ + public String getTransactionID() { + return transactionID; + } + + /** + * If isSuccess() == false this will return the error message. + *

+ * If isSuccess() == true this will return NULL. + * + * @return + */ + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java new file mode 100644 index 000000000..4c980074d --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/CreditCardCommand.java @@ -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. + *

+ * No fallback implemented as a credit card failure must result in an error as no logical fallback exists. + *

+ * This implementation originated from a functional HystrixCommand wrapper around an Authorize.net API. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 { + 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 run() 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 HystrixCommand.run() 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 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 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(true); + } else { + return new Result(false); + } + + } + } + + public static class Result { + + 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 + } + +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java new file mode 100644 index 000000000..3802a4152 --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetOrderCommand.java @@ -0,0 +1,63 @@ +/** + * 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 com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch Order objects from a remote service or database. + *

+ * This fails fast with no fallback and does not use request caching. + */ +public class GetOrderCommand extends HystrixCommand { + + private final int orderId; + + public GetOrderCommand(int orderId) { + super(HystrixCommandGroupKey.Factory.asKey("Order")); + this.orderId = orderId; + } + + @Override + protected Order run() { + /* simulate performing network call to retrieve order */ + try { + Thread.sleep((int) (Math.random() * 200) + 50); + } catch (InterruptedException e) { + // do nothing + } + + /* fail rarely ... but allow failure as this one has no fallback */ + if (Math.random() > 0.9999) { + throw new RuntimeException("random failure loading order over network"); + } + + /* latency spike 5% of the time */ + if (Math.random() > 0.95) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 300) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create Order with data "from" the remote service response */ + return new Order(orderId); + } + +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java new file mode 100644 index 000000000..e3c03ae9c --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetPaymentInformationCommand.java @@ -0,0 +1,63 @@ +/** + * 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 com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch PaymentInformation objects from a remote service or database. + *

+ * This fails fast with no fallback and does not use request caching. + */ +public class GetPaymentInformationCommand extends HystrixCommand { + + private final UserAccount user; + + public GetPaymentInformationCommand(UserAccount user) { + super(HystrixCommandGroupKey.Factory.asKey("PaymentInformation")); + this.user = user; + } + + @Override + protected PaymentInformation run() { + /* simulate performing network call to retrieve order */ + try { + Thread.sleep((int) (Math.random() * 20) + 5); + } catch (InterruptedException e) { + // do nothing + } + + /* fail rarely ... but allow failure */ + if (Math.random() > 0.9999) { + throw new RuntimeException("random failure loading payment information over network"); + } + + /* latency spike 2% of the time */ + if (Math.random() > 0.98) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 100) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create (a very insecure) PaymentInformation with data "from" the remote service response */ + return new PaymentInformation(user, "4444888833337777", 12, 15); + } + +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java new file mode 100644 index 000000000..1f532e04b --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/GetUserAccountCommand.java @@ -0,0 +1,131 @@ +/** + * 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.net.HttpCookie; + +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; + +/** + * Sample HystrixCommand simulating one that would fetch UserAccount objects from a remote service or database. + *

+ * This uses request caching and fallback behavior. + */ +public class GetUserAccountCommand extends HystrixCommand { + + private final HttpCookie httpCookie; + private final UserCookie userCookie; + + /** + * + * @param cookie + * @throws IllegalArgumentException + * if cookie is invalid meaning the user is not authenticated + */ + public GetUserAccountCommand(HttpCookie cookie) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("User"))); + this.httpCookie = cookie; + /* parse or throw an IllegalArgumentException */ + this.userCookie = UserCookie.parseCookie(httpCookie); + } + + @Override + protected UserAccount run() { + /* simulate performing network call to retrieve user information */ + try { + Thread.sleep((int) (Math.random() * 10) + 2); + } catch (InterruptedException e) { + // do nothing + } + + /* fail 5% of the time to show how fallback works */ + if (Math.random() > 0.95) { + throw new RuntimeException("random failure processing UserAccount network response"); + } + + /* latency spike 5% of the time so timeouts can be triggered occasionally */ + if (Math.random() > 0.95) { + // random latency spike + try { + Thread.sleep((int) (Math.random() * 300) + 25); + } catch (InterruptedException e) { + // do nothing + } + } + + /* success ... create UserAccount with data "from" the remote service response */ + return new UserAccount(86975, "John James", 2, true, false, true); + } + + /** + * Use the HttpCookie value as the cacheKey so multiple executions + * in the same HystrixRequestContext will respond from cache. + */ + @Override + protected String getCacheKey() { + return httpCookie.getValue(); + } + + /** + * Fallback that will use data from the UserCookie and stubbed defaults + * to create a UserAccount if the network call failed. + */ + @Override + protected UserAccount getFallback() { + /* + * first 3 come from the HttpCookie + * next 3 are stubbed defaults + */ + return new UserAccount(userCookie.userId, userCookie.name, userCookie.accountType, true, true, true); + } + + /** + * Represents values containing in the cookie. + *

+ * A real version of this could handle decrypting a secure HTTPS cookie. + */ + private static class UserCookie { + /** + * Parse an HttpCookie into a UserCookie or IllegalArgumentException if invalid cookie + * + * @param cookie + * @return UserCookie + * @throws IllegalArgumentException + * if cookie is invalid + */ + private static UserCookie parseCookie(HttpCookie cookie) { + /* real code would parse the cookie here */ + if (Math.random() < 0.998) { + /* valid cookie */ + return new UserCookie(12345, "Henry Peter", 1); + } else { + /* invalid cookie */ + throw new IllegalArgumentException(); + } + } + + public UserCookie(int userId, String name, int accountType) { + this.userId = userId; + this.name = name; + this.accountType = accountType; + } + + private final int userId; + private final String name; + private final int accountType; + } +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java new file mode 100644 index 000000000..daf2b8b26 --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/HystrixCommandDemo.java @@ -0,0 +1,92 @@ +/** + * 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 java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; + +/** + * Executable client that demonstrates the lifecycle, metrics, request log and behavior of HystrixCommands. + */ +public class HystrixCommandDemo { + + public static void main(String args[]) { + /* + * Instead of using injected properties we'll set them via Archaius + * so the rest of the code behaves as it would in a real system + * where it picks up properties externally provided. + */ + ConfigurationManager.getConfigInstance().setProperty("hystrix.threadpool.default.coreSize", 8); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.CreditCardCommand.execution.isolation.thread.timeoutInMilliseconds", 3000); + ConfigurationManager.getConfigInstance().setProperty("hystrix.command.GetUserAccountCommand.execution.isolation.thread.timeoutInMilliseconds", 50); + + new HystrixCommandDemo().run(); + + } + + /* + * Thread-pool to simulate HTTP requests. + * + * Use CallerRunsPolicy so we can just keep iterating and adding to it and it will block when full. + */ + private final ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 5, TimeUnit.DAYS, new SynchronousQueue(), new ThreadPoolExecutor.CallerRunsPolicy()); + + public void run() { + while (true) { + executeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment(); + } + } + + public void executeSimulatedUserRequestForOrderConfirmationAndCreditCardPayment() { + pool.execute(new Runnable() { + + @Override + public void run() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + /* fetch user object with http cookies */ + UserAccount user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute(); + + /* fetch the payment information (asynchronously) for the user so the credit card payment can proceed */ + Future paymentInformation = new GetPaymentInformationCommand(user).queue(); + + /* fetch the order we're processing for the user */ + int orderIdFromRequestArgument = 13579; + Order previouslySavedOrder = new GetOrderCommand(orderIdFromRequestArgument).execute(); + + CreditCardCommand credit = new CreditCardCommand(previouslySavedOrder, paymentInformation.get(), new BigDecimal(123.45)); + credit.execute(); + + System.out.println("Request => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + context.shutdown(); + } + } + + }); + + } +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java new file mode 100644 index 000000000..77ab03798 --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/Order.java @@ -0,0 +1,35 @@ +/** + * 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.net.HttpCookie; + +/** + * POJO + */ +public class Order { + + private final int orderId; + private UserAccount user; + + public Order(int orderId) { + this.orderId = orderId; + + /* a contrived example of calling GetUserAccount again */ + user = new GetUserAccountCommand(new HttpCookie("mockKey", "mockValueFromHttpRequest")).execute(); + } + +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java new file mode 100644 index 000000000..3bb085da8 --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/PaymentInformation.java @@ -0,0 +1,47 @@ +/** + * 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 + */ +public class PaymentInformation { + + private final UserAccount user; + private final String creditCardNumber; + private final int expirationMonth; + private final int expirationYear; + + public PaymentInformation(UserAccount user, String creditCardNumber, int expirationMonth, int expirationYear) { + this.user = user; + this.creditCardNumber = creditCardNumber; + this.expirationMonth = expirationMonth; + this.expirationYear = expirationYear; + } + + public String getCreditCardNumber() { + return creditCardNumber; + } + + public int getExpirationMonth() { + return expirationMonth; + } + + public int getExpirationYear() { + return expirationYear; + } + +} diff --git a/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java new file mode 100644 index 000000000..83175ec75 --- /dev/null +++ b/hystrix-examples/src/main/java/com/netflix/hystrix/examples/demo/UserAccount.java @@ -0,0 +1,63 @@ +/** + * 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; + +/** + * Simple POJO to represent a user and their metadata. + */ +public class UserAccount { + + private final int userId; + private final String name; + private final int accountType; + private final boolean isFeatureXenabled; + private final boolean isFeatureYenabled; + private final boolean isFeatureZenabled; + + public UserAccount(int userId, String name, int accountType, boolean x, boolean y, boolean z) { + this.userId = userId; + this.name = name; + this.accountType = accountType; + this.isFeatureXenabled = x; + this.isFeatureYenabled = y; + this.isFeatureZenabled = z; + } + + public int getUserId() { + return userId; + } + + public String getName() { + return name; + } + + public int getAccountType() { + return accountType; + } + + public boolean isFeatureXenabled() { + return isFeatureXenabled; + } + + public boolean isFeatureYenabled() { + return isFeatureYenabled; + } + + public boolean isFeatureZenabled() { + return isFeatureZenabled; + } + +}