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

Writing testable serverless apps using hexagonal architecture with Hexagonal Architecture

Writing testable serverless apps using hexagonal architecture with Hexagonal Architecture

AWS UGs Brazil: Serverless Week 2020 (http://www.serverlessweek.com)

2663a9ac90cc6ed420e0e1560db57782?s=128

Slobodan Stojanović

October 28, 2020
Tweet

Transcript

  1. What's the scariest thing about serverless?

  2. @slobodan_ Long-running tasks?

  3. @slobodan_ Compliance?

  4. @slobodan_ Using binaries and large dependencies?

  5. @slobodan_ Coldstart?

  6. @slobodan_ Cold start with VPC?

  7. @slobodan_ Local development and debugging?

  8. @slobodan_ Losing control?

  9. @slobodan_ Node.js?

  10. @slobodan_ But, what about…

  11. @slobodan_ BIG

  12. @slobodan_ BAD

  13. @slobodan_ VENDOR LOCK-IN

  14. @slobodan_ What is vendor lock-in?

  15. @slobodan_ "In economics, vendor lock-in, makes a customer dependent on

    a vendor for products and services, unable to use another vendor without substantial switching costs."
  16. @slobodan_

  17. @slobodan_

  18. @slobodan_

  19. @slobodan_ A guy with a lot of servers. Let's call

    him Jeff.
  20. @slobodan_ A guy with a lot of servers. Let's call

    him Jeff.
  21. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  22. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  23. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  24. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  25. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  26. @slobodan_ Jeff is smart, and he knows how do you

    use his servers.
  27. @slobodan_ But what if Jeff is actually a villain?

  28. @slobodan_ Your wallet would not be happy…

  29. @slobodan_ Another guy with a lot of servers. Let's call

    him Bill.
  30. @slobodan_ Another guy with a lot of servers. Let's call

    him Bill.
  31. @slobodan_ Another guy with a lot of servers. Let's call

    him Bill.
  32. @slobodan_ Another guy with a lot of servers. Let's call

    him Bill.
  33. @slobodan_ That's vendor lock-in in the cloud

  34. @slobodan_ "My train of thought went like this: the term

    “lock-in” is misleading. We are really talking about switching costs." Mark Schwartz Enterprise Strategist at AWS
  35. @slobodan_ "As soon as you commit yourself to a platform

    or a vendor you will have switching costs if you later decide to change." Mark Schwartz Enterprise Strategist at AWS
  36. @slobodan_ Howtofight vendorlock-in?

  37. @slobodan_ Or, how to keep your switching costs reasonable?

  38. @slobodan_ • Planning and analysis • Good architecture • Deployment

    procedures How likely wi! I n"d to switch? What would be the cost?
  39. @slobodan_ That leads us to our topic…

  40. Writing testable serverless apps and preventing vendor lock-in using hexagonal

    architecture
  41. @slobodan_ But, before we continue…

  42. Slobodan Stojanovic CTO @ Cloud Horizon & CTO @ Vacation

    Tracker co-author of Serverless Applications with Node.js book AWS Serverless Hero @slobodan_
  43. @slobodan_ Writing testable serverless apps using hexagonal architecture

  44. @slobodan_ Why is testing important for serverless apps?

  45. @slobodan_ Most of the time serverless apps are not fully

    isolated monoliths without integrations
  46. @slobodan_ Instead, they contain many services interacting with each other

    and with external dependencies
  47. @slobodan_ An Example: Vacation Tracker

  48. @slobodan_ VacationTracker.io

  49. @slobodan_

  50. @slobodan_ Integrations can change or fail every moment!

  51. @slobodan_

  52. @slobodan_

  53. @slobodan_

  54. @slobodan_

  55. @slobodan_

  56. @slobodan_ Tests don't prevent changes. They make sure your changes

    are not accidental.
  57. @slobodan_ But how do we prevent changes?

  58. @slobodan_ We can't. Our app needs to adapt fast! But

    we'! discuss that later today.
  59. @slobodan_ But how do you know what should you test

    in a serverless app?
  60. @slobodan_ Testing pyramid

  61. @slobodan_

  62. @slobodan_ Testing pyramid vs "Serverless testing pyramid"

  63. @slobodan_

  64. @slobodan_ Integration tests are cheaper, but also more important, because

    the common serverless app is split into many small pieces
  65. @slobodan_ Writing testable serverless apps using hexagonal architecture

  66. @slobodan_ Ok, so which architecture is the best for serverless

    apps?
  67. @slobodan_ Any architecture that will let you test your serverless

    app easily and keep switching costs low.
  68. @slobodan_ Because sooner or later you'll need to switch/migrate pieces

    of your app.
  69. @slobodan_ Not to another cloud vendor, but to your new

    service, new or changed integration…
  70. @slobodan_ Risks to consider when building a serverless app

  71. @slobodan_ • configuration risks • technical workflow risks • business

    logic risks • integration risks
  72. @slobodan_ One of the architectures that fits these needs is

    Hexagonal Architecture or Ports and Adapters
  73. @slobodan_ Writing testable serverless apps using hexagonal architecture

  74. @slobodan_ "Allow an application to equally be driven by users,

    programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases." Alistair Cockburn Creator of Hexagonal architecture
  75. @slobodan_

  76. @slobodan_

  77. @slobodan_ Let's go back to Vacation Tracker for an example

  78. @slobodan_

  79. @slobodan_

  80. @slobodan_

  81. @slobodan_

  82. @slobodan_

  83. @slobodan_

  84. @slobodan_ Code, please!

  85. @slobodan_ const { httpResponse, parseApiEvent, EventBridgeRepository } = require('../common') const

    main = require('./main') export async function handler(event) { // Create instance of SNS notification repository const notification = new EventBridgeRepository( process.env.topic ) // Invoke main function with all dependencies await main(event, parseApiEvent, notification) return httpResponse() }
  86. @slobodan_ await main(event, parseApiEvent, notification)

  87. @slobodan_ Unit tests

  88. @slobodan_ await main(event, parseApiEvent, notification) Mock notification repository instance Some

    static values Parser mock
  89. @slobodan_ Integration tests

  90. @slobodan_ await main(event, parseApiEvent, notification) Local notification adapter, using JS

    events for example Some static values Parser function
  91. @slobodan_ await main(event, parseApiEvent, notification) EventBridge notification adapter has its

    own integration tests
  92. @slobodan_ Simple and nice

  93. @slobodan_ What about End-to-End and UI tests?

  94. @slobodan_ Serverless is mainly a back end thing, but it

    can help with UI tests!
  95. @slobodan_ UI tests are slow and expensive

  96. @slobodan_ Benefits of serverless are cheap infrastructure and easy/fast parallelization

  97. @slobodan_

  98. @slobodan_ Or, you can use your favorite tool, such as

    cypress.io
  99. @slobodan_ But, do you remember…

  100. @slobodan_ BIG

  101. @slobodan_ BAD

  102. @slobodan_ VENDOR LOCK-IN

  103. @slobodan_

  104. @slobodan_ How does hexagonal architecture help you fightingvendorlock-in?

  105. @slobodan_ How does hexagonal architecture help you to keep switching

    costs reasonable?
  106. @slobodan_ Story time

  107. @slobodan_ Vacation Tracker VacationTracker.io

  108. @slobodan_ • Serverless prototype • Small team (1 fulltime developer)

    • Initial product was Serverless chatbot + Express.js and MongoDB • Growing fast (200+ teams using it)
  109. @slobodan_ + A few bad decisions as a bonus :)

  110. @slobodan_ We did a few migrations in past few months.

  111. @slobodan_ • Express API -> Serverless API migration • MongoDB

    -> DynamoDB migration For example:
  112. @slobodan_ Let's talk about MongoDB -> DynamoDB switch

  113. @slobodan_ We defined an interface for our MongoDB Repository.

  114. @slobodan_ For example, this: Returns a single user with its

    properties. const db = new MongoDbRepository(something) const user = db.getUser(userId)
  115. @slobodan_ We created DynamoDB Repository with the same interface.

  116. @slobodan_ Returns a single user with the same properties. const

    mdb = new MongoDbRepository(something) const ddb = new DynamoDbRepository(somethingElse) const user1 = mdb.getUser(userId) const user2 = ddb.getUser(userId) expect(user1).toEqual(user2) // They are equal! For example, this:
  117. @slobodan_ So we simply did the following:

  118. @slobodan_

  119. @slobodan_

  120. @slobodan_

  121. @slobodan_ But, how does this look like?

  122. @slobodan_ describe('DynamoDB repository', () => { describe('unit', () => {

    ... }) describe('integration', () => { beforeAll(() => { // Create test DB }) afterAll(() => { // Destroy test DB }) // Tests }) })
  123. @slobodan_ beforeAll(async () => { const params = { ...

    } await dynamoDb.createTable(params).promise() await dynamoDb.waitFor('tableExists', { TableName: tableName }).promise() })
  124. @slobodan_ afterAll(async () => { await dynamoDb.deleteTable({ TableName: tableName }).promise()

    await dynamoDb.waitFor('tableNotExists', { TableName: tableName }).promise() })
  125. @slobodan_ And we lived happily ever after…

  126. @slobodan_ Writing testable serverless apps using hexagonal architecture Beyond testing

  127. @slobodan_ What should we do with things that can't be

    tested? For example, Slack changes an API while your app is in production
  128. @slobodan_ Make sure you are monitoring your app and tracking

    errors
  129. @slobodan_ Monitoring/error-tracking tools

  130. @slobodan_ • Built-in tools (CloudWatch, X-Ray) • Epsagon • Thundra

    • New Relic (they bought IOpipe) • Lumigo • and many others
  131. @slobodan_ Serverless apps often heavily relies on front end, make

    sure you track front end errors too!
  132. @slobodan_ Also, services are smaller and smaller, but integrations require

    fine grained permissions.
  133. @slobodan_ There are tools that can help you to improve

    your permissions and keep your app secure.* * Protego, Puresec, and others.
  134. @slobodan_ But sometimes even monitoring can't help you!

  135. @slobodan_ You'll need a direct communication with your end users

    all the time!
  136. @slobodan_

  137. @slobodan_ Summary

  138. @slobodan_ • Good architecture helps you to maintain your switching

    costs low (or at least reasonable) • Hexagonal architecture is a nice fit for serverless apps • Test your integrations (and app in general) • Testing is not enough, you'll need monitoring and error tracking for your serverless apps
  139. @slobodan_