Upgrade to Pro — share decks privately, control downloads, hide ads and more …

José San Román Álvarez de Lara_Bank on the outs...

Codemotion
September 24, 2019

José San Román Álvarez de Lara_Bank on the outside, start up on the inside, developing software @ scale_Codemotion Madrid 2019

Building decoupled software is essential when you want to maintain velocity while developing a product in a global, distributed way. However, following the wrong parameters will handicap your quality assurance. How to be sure your product works? I’m a staunch advocate of using unit tests. They are faster and less flaky than integrated tests and allow you to scale up like a startup. Don’t be fooled! Integrated tests will kill your product. Among other things I will introduce the concept of contract testing and show you examples of PACT, the most suitable Java library to implement this concept.

About:
José San Román Álvarez de Lara, CIO, ING Bank España

José is the Chief Information Officer (CIO) of ING Bank in Spain, responsible for developing the local IT strategy and aligning it with ING’s global ambition to build the digital bank of the future. Before joining ING as a software architect in 2005, he helped various tech companies to construct complex applications and systems using advanced software engineering methodologies, tools and practices. In particular he’s an avid fan of Agile, XP, devops and continuous delivery. Passionate about innovation and technology, he also loves MTB downhill and occasionally runs.

Codemotion

September 24, 2019
Tweet

More Decks by Codemotion

Other Decks in Technology

Transcript

  1. Bank from the outside, startup in the inside Developing software

    @ scale José San Román Álvarez de Lara (CIO ING Spain) 24-25 September, 2019
  2. Codebase grows Not easy to assess impact of changes Minimum

    impact work style Complexity Lower speed
  3. Shares state via a clear contract Built, test & deployed

    independently Does not require big knowledge to consume Decoupled software
  4. Platform /ˈplatfɔːm/ “A raised level surface on which people or

    things can stand” (*) Source: https://www.lexico.com/en/definition/platform
  5. 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
  6. Mortgages Loans Debit card Risk engine Account Management System Simulation

    & data collection Origination Servicing Case manager
  7. 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
  8. 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
  9. ~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
  10. Contract Provider contract test Provider verifier Provider Request Response Consumer

    contract test Consumer Provider Mock Request Response
  11. <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>
  12. 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 } }
  13. @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(); } }
  14. @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()); } }
  15. @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) {[…]} }
  16. @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())); } }
  17. @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()); } [….] } }