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

Writing Testable Serverless Apps Using Hexagonal Architecture @ TestJS Summit 2021

Writing Testable Serverless Apps Using Hexagonal Architecture @ TestJS Summit 2021

2663a9ac90cc6ed420e0e1560db57782?s=128

Slobodan Stojanović

January 29, 2021
Tweet

Transcript

  1. What's the scariest thing about serverless?

  2. @slobodan_ Long-running tasks?

  3. @slobodan_ Coldstart?

  4. @slobodan_ Local development and debugging?

  5. @slobodan_ Losing control?

  6. @slobodan_ But, what about…

  7. @slobodan_ BIG

  8. @slobodan_ BAD

  9. @slobodan_ VENDOR LOCK-IN

  10. @slobodan_ What is vendor lock-in?

  11. @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."
  12. @slobodan_

  13. @slobodan_

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

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

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

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

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

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

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

  21. @slobodan_ But what if Jeff is actually a villain?

  22. @slobodan_ Your wallet would not be happy…

  23. @slobodan_

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

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

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

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

  28. @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
  29. @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
  30. @slobodan_ Howtofight vendorlock-in?

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

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

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

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

    architecture
  35. @slobodan_ But, before we continue…

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

    Tracker co-author of Serverless Applications with Node.js book AWS Serverless Hero https://slobodan.me
  37. @slobodan_ Writing testable serverless apps using hexagonal architecture

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

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

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

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

  42. @slobodan_ VacationTracker.io

  43. @slobodan_

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

  45. @slobodan_

  46. @slobodan_

  47. @slobodan_

  48. @slobodan_

  49. @slobodan_

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

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

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

  53. @slobodan_ But how do you know what should you test

    in a serverless app?
  54. @slobodan_ Testing pyramid

  55. @slobodan_

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

  57. @slobodan_

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

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

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

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

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

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

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

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

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

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

  68. @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
  69. @slobodan_

  70. @slobodan_

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

  72. @slobodan_

  73. @slobodan_

  74. @slobodan_

  75. @slobodan_

  76. @slobodan_

  77. @slobodan_

  78. @slobodan_

  79. @slobodan_ Code, please!

  80. @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() }
  81. @slobodan_ await main(event, parseApiEvent, notification)

  82. @slobodan_ Unit tests

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

    static values Parser mock
  84. @slobodan_ Integration tests

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

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

    own integration tests
  87. @slobodan_ Simple and nice

  88. @slobodan_ But, do you remember…

  89. @slobodan_ BIG

  90. @slobodan_ BAD

  91. @slobodan_ VENDOR LOCK-IN

  92. @slobodan_

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

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

    costs reasonable?
  95. @slobodan_ Story time

  96. @slobodan_ Vacation Tracker VacationTracker.io

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

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

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

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

    -> DynamoDB migration • API Gateway -> AppSync and GraphQL For example:
  101. @slobodan_ Let's talk about MongoDB -> DynamoDB switch

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

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

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

  105. @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:
  106. @slobodan_ So we simply did the following:

  107. @slobodan_

  108. @slobodan_

  109. @slobodan_

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

  111. @slobodan_ describe('DynamoDB repository', () => { beforeAll(() => { //

    Create test DB }) afterAll(() => { // Destroy test DB }) // Tests })
  112. @slobodan_ beforeAll(async () => { const params = { ...

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

    await dynamoDb.waitFor('tableNotExists', { TableName: tableName }).promise() })
  114. @slobodan_

  115. @slobodan_

  116. @slobodan_ Summary

  117. @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
  118. @slobodan_

  119. @slobodan_ https://slobodan.me/subscribe