Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Bank on the outside, start-up on the inside: developing software @ scale

935 views

Published on

José San Román Álvarez de Lara, CIO ING Bank Spain presented at Codemotion in Madrid on September 24, 2019.

Published in: Engineering
  • Be the first to comment

Bank on the outside, start-up on the inside: developing software @ scale

  1. 1. Bank from the outside, startup in the inside Developing software @ scale José San Román Álvarez de Lara / CIO ING Bank Spain Madrid | September 24 - 25, 2019
  2. 2. Developing software @scale
  3. 3. +50 teams having the speed and the customer focus of a start-up
  4. 4. As software ages and grows it appears to atrophy
  5. 5. Codebase grows Not easy to assess impact of changes Minimu m impact work style Complexity Lower speed
  6. 6. How to scale?
  7. 7. Each piece should be a complete system itself
  8. 8. Decoupled software
  9. 9. Shares state via a clear contract Built, test & deployed independently Does not require big knowledge to consume Decoupled software
  10. 10. Decoupled teams
  11. 11. Each team is a complete organization itself
  12. 12. How the team implements software should not be important outside the team
  13. 13. Rely on a common platform to ensure proper alignment
  14. 14. Platform /ˈplatfɔːm/ “A raised level surface on which people or things can stand” (*) Source: https://www.lexico.com/en/definition/platform
  15. 15. Organization
  16. 16. Conway’s law “… organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.” (*) Source: http://www.melconway.com/Home/Conways_Law.html
  17. 17. Inverting the Conway’s law
  18. 18. Build an organization that promotes your desired architecture
  19. 19. Mortgages Loans Debit card Risk engine Account Management System Simulation&data collection Origination Servicing Case manager
  20. 20. Handicaps
  21. 21. Decoupled organization handicaps More complexity in operations Dependencies may slow you down The system is not easy to test
  22. 22. Complex operations
  23. 23. Business functionality Configuration Discovery Security Monitoring Logging Networking …
  24. 24. Rely on a platform
  25. 25. Dependencies
  26. 26. Economy of scale does not apply to long-term software
  27. 27. Reuse only due to strong requirements
  28. 28. Avoid inter-domain dependencies
  29. 29. Allow dependencies only on vertical slices
  30. 30. Mortgages Loans Debit card Risk engine Account Management System Dependencies
  31. 31. Apply the dependency inversion principle @ organization level
  32. 32. Mortgages Loans Debit card Risk engine Account Management System Dependencies
  33. 33. Not easy to test
  34. 34. Integrated tests “any test whose result (success of fail) depends on the correctness of the implementation of more than one piece of non-trivial behaviour” (*) Source: https://blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam
  35. 35. Integrated tests are very very flaky
  36. 36. Flaky tests by Google (*) Source: Google’s testing blog https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html
  37. 37. Flaky tests will kill your speed
  38. 38. Team starts writing unit tests We find bugs Team writes some integrations tests We still find bugs Team writes some UI tests Integration and/or UI tests becomes slow, unstable and not easy to maintain Team starts disabling tests
  39. 39. Isolated tests “tests that does not have any side effects”
  40. 40. Unit tests are the better isolated tests
  41. 41. ~77% #bugs found in production can be detected using unit tests (*) Simple Testing Can Prevent Most Critical Failures: https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf
  42. 42. Can we use unit tests to check the integrations points?
  43. 43. Use stubs (canned response) to test behaviour
  44. 44. Use mocks (expectations) to test collaboration
  45. 45. How to check your mocking assumptions?
  46. 46. “Don’t mock what you don’t own”
  47. 47. A contract includes the syntax & the semantics
  48. 48. Interface & behaviour
  49. 49. Consumer driven
  50. 50. Dependency inversion
  51. 51. Contract Provider contract test Provider verifier Provider Request Response Consumer contract test Consumer Provider Mock Request Response
  52. 52. Same confidence Lower costs Easy to maintain Very stable
  53. 53. Hexagonal architecture
  54. 54. Services Domain Repositories Third parties Resources Database Provider’s contracts
  55. 55. Domain behaves as expected Domain collaborates as expected Consumed third parties comply with contracts
  56. 56. Domain behaves as expected
  57. 57. Services Domain Stubbed repositories Stubbed third parties Resources Database Provider’s contracts Behaviour unit tests
  58. 58. Domain collaborates as expected
  59. 59. Services Domain Stubbed repositories Mocked third parties Resources Database Provider’s contracts Collaboration unit tests
  60. 60. Third parties comply the contracts
  61. 61. Services Domain Repositories Third parties Resources Database Stubbed provider’s contracts Consumer contract unit tests
  62. 62. Stubbed services Domain Repositories Third parties Resources Database Provider’s contracts Provider contract unit tests
  63. 63. Move money Risk engine
  64. 64. <artifactId>move-money</artifactId> <parent> <artifactId>codemotion-2019</artifactId> <groupId>com.ing.jsr</groupId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> […] <dependency> <groupId>au.com.dius</groupId> <artifactId> pact-jvm-consumer-java8_2.12 </artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>au.com.dius</groupId> <artifactId> pact-jvm-consumer-junit_2.12</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> </dependencies> <artifactId>risk-engine</artifactId> <parent> <artifactId>codemotion-2019</artifactId> <groupId>com.ing.jsr</groupId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> […] <dependency> <groupId>au.com.dius</groupId> <artifactId>pact-jvm-provider-junit_2.12</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>au.com.dius</groupId> <artifactId>pact-jvm-provider-spring_2.12</artifactId> <version>${pact.version}</version> <scope>test</scope> </dependency> </dependencies>
  65. 65. public class MoveMoneyTest { @Rule public PactProviderRuleMk2 riskEngine = new PactProviderRuleMk2("risk-engine", PactSpecVersion.V3, this); @Pact(provider = "risk-engine", consumer = "move-money") public RequestResponsePact pactForHighRiskOperation(PactDslWithProvider builder) { // See source code in next slides } @PactVerification(fragment = "pactForHighRiskOperation") @Test public void testHighRiskOperation() { // See source code in next slides } }
  66. 66. @Pact(provider = "risk-engine", consumer = "move-money") public RequestResponsePact pactForHighRiskOperation(PactDslWithProvider builder) { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE); return builder .given("A high risk operation") .uponReceiving("A request to run a risk validation") .path("/api/validate").method(RequestMethod.POST.name()) .body(newJsonBody((o) -> { o.stringValue("channel", Channel.WEB.toString()); o.stringValue("source", RiskEngineInfoBuilder.SOURCE_ACCOUNT); o.stringValue("dest", RiskEngineInfoBuilder.DEST_ACCOUNT); o.decimalType("quantity", RiskEngineInfoBuilder.HIGH_QUANTITY); }).build()) .willRespondWith().headers(headers).status(200) .body(newJsonBody((o) -> { o.stringValue("riskLevel", RiskLevel.HIGH.toString()); }).build()) .toPact(); } }
  67. 67. @Test @PactVerification(fragment = "pactForHighRiskOperation") public void testHighRiskOperation() { RiskEngine engine = new RiskEngineRestImpl(riskEngine .getUrl()); RiskEngineInfo info = RiskEngineInfoBuilder.create() .forWebChannel() .withAHighAmmount() .build(); RiskEngineResult result = engine.validate(info); assertEquals(result.getRiskLevel(), RiskLevel.HIGH.toString()); } }
  68. 68. @RestController public class RiskEngineController { @Autowired private RiskEngine engine; @PostMapping(path = "/api/validate") public RiskEngineResult validate(@RequestBody RiskEngineInfo riskInfo) { return engine.validate(riskInfo); } } @Service public class RiskEngine { @Autowired private BadAccountsRepository badAccounts; @Autowired private KnownAccountsRepository knownAccounts; public RiskEngineResult validate(RiskEngineInfo info) {[…]} private void calculateRiskDueToDestionation (RiskEngineInfo info, RiskEngineResult result){[…]} private void calculateRiskDueToAmmount (RiskEngineInfo info, RiskEngineResult result) {[…]} }
  69. 69. @RunWith(SpringRestPactRunner.class) @Provider("risk-engine") @PactFolder("pacts") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class RiskEngineContractTests { @MockBean private RiskEngine engine; @TestTarget public final Target target = new SpringBootHttpTarget(); @State(”A high risk operation") public void highRiskOperation() { when(engine.validate(any())) .thenReturn(new RiskEngineResult().setRiskLevel(RiskLevel.HIGH.toString())); } }
  70. 70. @RunWith(SpringRunner.class) @SpringBootTest public class RiskEngineTests { @MockBean private BadAccountsRepository badAccounts; @MockBean private KnownAccountsRepository knownAccounts; @Autowired private RiskEngine riskEngine; @Test public void testForbiddenOperationDueToBadAccount() { when(badAccounts.find(any())).thenReturn(true); when(knownAccounts.find(any())).thenReturn(false); RiskEngineInfo info = RiskEngineInfoBuilder.create().forWebChannel().withALowAmmount().build(); RiskEngineResult risk = riskEngine.validate(info); assertEquals(RiskLevel.FORBBIDEN.toString(), riskEngine.validate(info).getRiskLevel()); } [….] } }
  71. 71. Takeaways
  72. 72. Build decoupled software to ensure you are able to maintain your speed
  73. 73. Organize teams in a way that promotes your desired architecture
  74. 74. Ensure you simplify your operations relying on a platform
  75. 75. Limit & manage your dependencies
  76. 76. Write isolated tests Avoid integrated tests
  77. 77. Use contract tests to check integration points and validate mocking assumptions
  78. 78. Invert dependencies applying consumer driven
  79. 79. Thks! ;-)
  80. 80. Questions?

×