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

Microservices From The Trenches

Microservices From The Trenches

Racconto come facciamo Microservices con Continuous Delivery, ports and adapter architecture, event sourcing, cdc testing in Kotlin con cloudfoundry.

Uberto Barbini

April 15, 2019
Tweet

More Decks by Uberto Barbini

Other Decks in Programming

Transcript

  1. Who we are Content Acquisition: ~3800 Journals ~350,000 Accepted submissions

    for year New Peer Review system New Rights and Payments collection
  2. Kanban and Daily Standup • Physical board for tracking dev

    and qa • Jira for Managers and Business Analysts
  3. CI/CD Pipelines Automated • Each commit can potentially recreate all

    pipelines • No need to manually configure CI server • Each MicroService declare its dependencies • Our in house solution is not perfect but quite good
  4. Trunk Based Development Pairing and TDD • Pairing enforce good

    habits • No code review and pull requests • Tripling and Mobbing as well • Remote pairing with our team in India • Slack channels for pairs comunications • Whiteboard discussions all the time
  5. fun main() { Bootstrap().start(AlpacaCreator) } object AlpacaCreator : ApplicationCreator {

    override fun invoke(bootstrap: Bootstrap): Application { val monitor = LoggingMonitor(bootstrap.logging) val eventStore = PgEventStore<AuthorApprovalEvent>( DatabaseTransactor(bootstrap.configuration), monitor ) val sparkPostClient = SparkPostClient( bootstrap.createHttp4kClient(SparkPost), bootstrap.configuration[OperationalKeys.SPARKPOST_SECURITY_TOKEN] ) val emailNotifier = EmailNotifier( sparkPostClient, monitor ) val appHandler = Alpaca( CommandHandler(eventStore, emailNotifier), DashboardProjection(eventStore)) ) return Application( bootstrap.createHttp4kServer(appHandler)) } From Adapters From Domain
  6. Mono Repo • Single Git repository • Split in modules

    with source dependency – Underware: useful snippets, no dependencies – Domain(s): depend only on underware – Adapters: depend on domain but not on other adapters – Executables: connecting a domain with one or more adapters, almost no code
  7. Separated pipelines • No artifact repository • No versioning, no

    SNAPSHOT jars • Independent release of microservices • Code reuse, no frameworks • Easy to refactor, change and test • Working on insulating better Bounded Contexts
  8. Monitoring Logging • ELK (the usual sospect) • Tracing Monitor

    Zipkin (TraceId + SpanId) • Dedicated Monitor Events (No Log4j or similar, no log levels) • As little as possible when things work As much as possible when there is an error
  9. Testing • Unit tests, TDD • External Integration Tests •

    Database Migration Tests • UI Automation Tests • Consumer Driven Contract Tests • Domain Driven Tests
  10. Consumer Driven Contract Testing • Module A declares that it

    consumes B api • A CDC test in A verify that A works with a B in- memory implementation • When B change, it runs all cdc tests from all modules that are B consumers
  11. abstract class ProductionSystemContract { abstract val productionClient: ProductionSystem abstract val

    validSubmissionId: AcceptedSubmissionId @Rule @JvmField val allowRunInEnvironments = AllowRunInEnvironments @Test @RunInEnvironments(local, test, staging) fun `Releases an accepted submission in the production workflow`() { productionClient.releaseForPublication(validSubmissionId).expectSuccess() } @Test @RunInEnvironments(local, test, staging) fun `Errors for an invalid accepted submission`() { productionClient.releaseForPublication(AcceptedSubmissionId("unknown")).expectFailure() }} … interface ProductionSystem { fun releaseForPublication(id: AcceptedSubmissionId): Result<ErrorCode, Unit> }
  12. Domain Driven Testing • A test to describe a use

    case from a scenario • The personas in the test are Roles interfaces • Roles have an implementations which use adapters and Http/Db/Files etc. • Roles have another implementation which use only domain objects • Tests verify that the logic is correct and that there is no business logic in the adapter layer
  13. class HandleAcceptedSubmissionManuallyTest { @Rule @JvmField val scenario = theApplication.newScenario() val

    peterProdEditor by lazy{ scenario.newProductionEditor() } val aliceTheAuthor by lazy{ scenario.newAuthor(aliceDetails, aliceId) } @WorkInProgress(until = "2019-04-15", by = "UB") @Test fun `Oasis processes a manuscript and releases it to production`() { peterProdEditor.`create a new author approval from a accepted submission`(acceptedSubmission) aliceTheAuthor.`has received an email containing the links to pdf forms to compile`(acceptedSubmission.id) peterProdEditor.`after receiving the email with a licence he can release it to production`(acceptedSubm ssion.id, LicenceType.OPEN_ACCESS) peterProdEditor.`see the submission returned on production`(acceptedSubmission.id) } } interface ProductionEditorRole { fun `after receiving the email with a licence he can release it to production`(acceptedSubmissionId… fun `see the submission returned on production`(acceptedSubmissionId: AcceptedSubmissionId) fun `create a new author approval from a accepted submission`(acceptedSubmission: AcceptedSubmission) }