Conf42 DevOps 2023 - Online

Let's Make a Pact - Don't Break my API!

Video size:

Abstract

In this talk, I’ll introduce bi-directional contract testing, which empowers teams scale fast by leveraging existing artifacts, such as OpenAPI, and tooling investments to understand what’s breaking, what’s not, and when a break is a good choice! Let microservices be everywhere with confidence! 60+% of organizations cite microservices as a leading driver for API growth in the next 2 years. Teams continue to break down monolithic systems, seeking to capitalize on the advantages of decoupled capabilities - reduced costs, reduced TTM, faster releases, decentralized evolvability. Such benefits don’t linearly scale! Managing the API sprawl is out weighting the benefits for many! Even with the solid extensibility design, governance workflows and high levels of automation, all of which leveraging OpenAPI and other leading specifications, it can be difficult to know what constitutes a breaking change to an API. What’s the impact? Teams providing APIs lose sight of who their consumers are. Consumers lose track of what surface area of APIs they are using. In fact, any observable change in the behavior of an API will be deemed breaking by certain consumers (Hyrum’s Law). Can it be addressed? Contact testing with Pact, empowers providers and consumers to determine the impact of any change from their IDE. It addresses the coordination & communication challenges associated with delivering distributed systems. Thus calming the chaos caused by the microservices sprawl. Bi-directional contract testing capabilities in Pactflow, hits the sweet spot with design-first flows. Using provider-driven contract testing, developers can quickly scale by leveraging existing OpenAPI documentation investments to create tests reusing the same OpenAPI artifacts. One contract and lots of Pacts with your consumers! Walk away with? - Challenges in scaling microservices - Brief history of Pact & contract testing - Understanding of what Contract Testing is and how to get started - Understand the consumer and provider workflow for safely making changes - Overview of bi-directional contract testing leveraging OpenAPI (and other specs) - See API contract testing in action! - Know where to go, to get involved or learn more!

Summary

  • How can we promote features through our APIs and evolve our APIs safely? We'll look at the relationship between the provider and the consumer in the modern API landscape.
  • Frank Kilcommins is an API technical evangelist at Smartbear. He'll explore how bidirectional contract testing can help us safely evolve APIs. Hopefully you'll leave this talk with some takeaways and a good understanding around bi directional contract testing.
  • Microservices are the main catalyst for the continued growth across the API landscape. As much as 50% of microservices will not be managed by traditional API management tooling by 2025. That means there's a high risk that they will become zombie APIs. Strong design practices are key to ensuring long living APIs.
  • If we're over relying on integration testing and into in testing, the testing pyramid is not really in hits optimal setup, especially for microservices. It's expensive to set up and maintain all of the underlying components and infrastructure. So delivering new value with the appropriate return on investment will be hard to achieve.
  • How can you manage the dependencies between all of the provider components, all. of the consumer components, and manage those across the different environments? It can become quite a headache. If you multiply this by more environments, by many more components, you will get to a situation that can be difficult to manage.
  • Bi directional contract testing is a way of testing microservices. It captures the interaction expectations between software component pairs. It supports a design first provider workflow, so it's well suited to starting on the provider side. It can help avoid many of the pitfalls that we have discussed up until now.
  • Bi directional contract testing can reduce your need for end to end testing and integration testing. It can significantly rebalance your costs with regards to testing and enables you to really safely evolve microservices. Hopefully you enjoyed my talk.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hi everyone. Welcome. So my talk is called let's make a pacts. Don't break my API. So I generally like to start this with a little bit of an icebreaker, kind of difficult in a virtual situation, but I just like you to reflect and see if you've ever been in a situation where you have broken an API for one of your consumers. And I definitely fall into that category. I've done that on numerous occasions. And maybe on the flip side of that situation, have you ever had a case where you've built out a client, you're depending on one or multiple APIs, and suddenly an API that you depend on stops working or starts behaving differently and you've received no communication from the API provider? Again, I've felt that pain more than once. So what I'm going to talk about today is really how to address that challenge. So how can we promote features through our APIs and evolve our APIs safely? And we'll look at the relationship between the provider and the consumer and the expectations that exist on both sides in the modern API landscape. So a little bit about me my name is Frank Kilcommins. I'm an API technical evangelist at Smartbear. I'm a software engineer and software architect by trade, and I'm very passionate about APIs and the developer experience surrounding APIs. And to explore the challenges that kind of I've laid out at the start, what I'll aim to do is just kind of take a brief look at what's happening across the API landscape and why things are probably going to get harder for all of us. We'll look at then how can you design for future feature promotion through your APIs and how you can do that safely? We'll maybe critically look at extensibility. So good extensibility patterns and can you succeed with extensibility alone? And probably the TLDR hint here is that you probably can't. So I'll introduce the concept of bidirectional contract testing and we'll explore how that can help us safely evolve APIs. I'll run through a demo showcasing how you can get started, what it's all about, and then hopefully you'll be able to leave the conference and leave this talk with some takeaways and a good understanding around bi directional contract testing. So microservices are everywhere. Of course, I think for most of us that have been involved or are involved in APIs or just general web development, we've seen the exponential growth of microservices and APIs over the last number of years. And we can look at all of the industry reports that come out, the ones that we produce ourselves at Smartbear, as well as those by our peers across the industry, like over at Postman. And we see that microservices are indeed identified as being the main catalyst for the continued growth across the API landscape. They're really more than just a fad. So for any of you that might follow kind of industry analysts like Gartner, you might be familiar with hype cycles and microservices really have left the threat of disillusionment. So we're seeing this continued shift within the most and the majority of organizations from kind of monolithic architectures to more consumable and capability focused architectures. And that's where microservices are, of course, very well suited. They help us unlock the value and the capabilities that's potentially hidden away within internal systems. So there's more and more of them being created and managing that increasing number of APIs and microservices is getting harder. And it's something that's often referred to as API sprawl. Now to deal with it. Up until this point in time, most of us have relied on traditional API management approaches to perform important activities, from acting as a central catalog for our APIs to improve discoverability, even enforcing base levels of assurance on the API. So the minimum amount of security that must be honored in order for the gateway to be able to process and pass through that API request, also acting as a hub for documentation and maybe even most importantly, acting as that matrix where we can go to, to understand what consumers are consuming and relying on what APIs and having kind of also that place where we can go, or that standard process for authorizing permissions towards APIs and services themselves. And if you're not experiencing any problem with API management as you stand today, then the chances are that you probably will in the near to midterm future. So more microservices will be created within your organization. And if we look at what Gartner are saying, they're saying the chances are as much as 50% of microservices will not be managed by traditional API management tooling by the year 2025. So that means there's a high risk that they will become zombie APIs, ones that are potentially poorly maintained, susceptible to security vulnerabilities. And more shadow APIs will pop up because we'll lose track of what APIs are there. So we'll reinvent the wheel within our organizations. The cognitive load, unfortunately for keeping track of all of this will fall upon the teams and it can be very, very easy to lose track quite quickly. And losing track and not being aware of who's consuming your APIs can in fact cause a blindness. And that has several implications. And the one I'm going to focus on in this talk today is that it's going to make the safe evolution of your APIs much more difficult. Now, one of the fundamental ways that we do our best to not break APIs and to introduce new changes and new features through APIs is to have strong design practices. Design first as an approach for microservices and APIs, ensures kind of thoughtful and let's say well defined and agreed design before we go and implement anything. And the earlier you can bake extensibility as a practice into your API delivery machine, the more likely it is that you will have long living APIs. And longevity is a good indicator. It's a good trait of a successful API. And here's kind of a little cheat sheet for API extensibility. And the first one is think about APIs from the outside in. So think about the consumer experience and what the consumer needs, and make sure that API that you're delivering is solving that consumer problem. Use specifications so ones like open API to describe the surface area and focus on good documentation to allow the consuming developers to ramp up as quickly as possible, effectively treating them like a product. Insofar as that you're prioritizing discoverability, usefulness and usability as attributes that will really aid and promote the adoption of your APIs and make it easier for them to be used and reused, because APIs that existing and are not consumed are absolutely useless. Moving more onto the tactile side of kind of things that I would recommend to do. What you need to do is define your extension points. And what I mean by that is that you will advertise to your consumers what parts of the API are stable versus what parts are maybe newer, maybe mvp, and they're more susceptible to change in the near term. So don't be afraid to communicate that upfront with your consumers. It can really go a long way to set the expectations, then communicate also an extensibility pattern. So by that I mean you should inform your consumers the rules that you would like them to follow if they encounter something new that they haven't seen before and must ignore, is probably the most common extensibility pattern out there. So that means that you want your clients to be tolerant, so you're informing them to say if you encounter something that you do not understand, then you must ignore it. Your client implementation must not break just because you do not understand something new. And then we'll do our work on the provider side to make sure that anything that we introduce is safe and it's a backwards compatible change, and it will not materially impact the functionality of what was there in the previous minor version. And then when I get onto that concept of minor versions, I think having a well defined and clearly communicated versioning strategy is good practice, and it's almost a base expectation on a consumer side. And semantic versioning is probably the most well known out there. And that sets again a clear expectation as to how you deal with patches, how you deal with minor backwards compatibility changes, and then how you also will deal with a breaking change. If in fact you have to make a breaking change to your API, testing for extensibility is great. Can you then even make it possible for your clients to peek ahead and test their client implementations to see if they're going to work with the next version of the API that you're about to roll out, and above all, communicate. So for each one of these points, communicate every time you make a change to your API, make sure you have a change log. Use that change log as a way to connect again with your consumers to pitch the value of the new features that you're adding and create that sense of fomo on their side and really pull them along with you throughout your API journey. On the don't side, most of these are very self explanatory, but don't add required inputs to the API because that would be a breaking change of course. Don't remove outputs or make them optional. Don't change the type of a property. So if you have a string today, make sure it's not can int tomorrow, and then jumping to the last one. Don't be inconsistent in your process. So every single time you're going through either an Apache fix, a bug fix, or a new feature enhancement to your API, follow a consistent process every single time. So if you do this, if you bake good extensibility hygiene into your API practices, if you're really thoughtful around the evolvability attributes, are you going to be successful? Well, unfortunately, probably not. So extensibility alone will not guarantee success. You can only achieve so much with interface design, no matter how good it is. And even with really solid extensibility techniques, the chances are that you will, in some shape or form break the expectations on a client side. And API design in general is a skill that's probably lacking to a certain degree within the landscape or within the API space. And that's showing up in some of the industry surveys that are out there as well. So there's a lack of good API designers in the market, which means it's more difficult for us to achieve high levels of good extensibility design. That doesn't mean that you shouldn't follow kind of the cheat sheet or the best practices with regards to extensibility. You absolutely should, because of course, good extensibility, good design hygiene allow us to hope for the best for a decent period of time. But with the acceleration of microservices growth across the landscape, we also need to be able to prepare for the worst. And we need to be aware of what's known as Hiram's law. And Hiram's law is, and I'll paraphrase, it's with a sufficient number of users of your API, it doesn't really matter what you promise in that interface design contract, all observable behavior of your system will be depended on by somebody. And that absolutely happens. So you will have some maybe undocumented behavior within your API that will be understood by consumers and they'll expect that that will continue to behave like that. And if that potentially was unintended, and you then address that, whats potentially could be a breaking change for someone, even though from a provider perspective you've never advertised that the API was meant to behave in that particular way. And the years of this can paralyze many providers. And we have a problem in the industry with major version proliferation. So as we lose track of who's consuming an API, what their explicit expectations are, it can really cause a little bit of a paralyzing effect on the API provider side. And then there's a knee jerk jerk reaction. Conf 42 create a major version of your API, which basically is saying, hey, we're not sure if this change is breaking or not. So to be safe, we'll create another version to make sure that any clients that are still connected to the previous version will remain up and running. And having multiple, but yet different versions of the same API incurs real costs on the team. It incurs cognitive costs with regards to be able to manage these things, but it also causes actual real costs for teams. So it's important to kind of break that in mind. So major version proliferation is a cost problem also for organizations, and that cost can be felt also on the testing side of, let's say the IT teams and the API teams. So zooming in on testing for a moment I think it's worth noting that if you're also employing the same testing strategy as you're maybe moving from a monolithic architecture over towards a microservices architecture, then probably you will not be successful. So delivering new value with the appropriate return on investment will be something that's hard to achieve. And what's depicted here is a very simplistic picture. So we have a consumer component and that is connecting and relying with an underlying microservice, called microservice one. And if I was to continue to employ, for example, an end to end testing strategy, which I was quite comfortable with in a monolithic architectural approach, and apply this to this specific setup which is depicted here, then every time I would want to make a small change to the consumer, I would have to stand up all of the underlying components in order to be able to execute those end to end tests. And I would also need to be able to manage all of the contextual data to allow those tests to run effectively. And that is a very costs exercise and it leads to what I call an unbalanced testing pyramid and testing approach. So if we're over relying on integration testing and into in testing, the testing pyramid is not really in hits optimal setup, especially for microservices. So it's expensive to set up and maintain all of the underlying components and infrastructure. It's also slow to get feedback. So you might be waiting for 30 minutes, even more for that pipeline to run in order to get the feedback that you want. It's unreliable. So managing all of that context and that data can lead to us ignoring failures. So have any of you ever been in the situation where you make changes, you run the tests, a test fails. You weren't really expecting that test to fail because it's not relating to the change that you made. You run it again, it passes. So then you're saying, yes, great, we can proceed. So what that actually means is that you've lost all confidence in the ability for your tests to be relied upon. So you really have an unreliable and expensive approach, and you also have an approach that's not very started. So it's difficult to just test explicitly what you're changing. You really have to stand up a complex underlying environment and you can spend a lot of time investigating false positives. And this brings us then on to dependency management for microservices. So how can you manage the dependencies between all of the provider components, all of the consumer components, and manage those across the different environments? It can become quite a headache. So what I have here is a very simplified CI setup. So two CI pipelines. So this is just for example purposes, so two cis across three environments, the CI environment, the staging environment and a production environment. So when I say CI, think of continuous integration. So GitHub actions, Azure DevOps pipelines, Jenkins and so forth. So anytime someone commits a change, this process will be kicked off. And here, what we could expect is that once all checks and balances are passed, we will be able to safely promote changes through towards production. So you will see that I have different versions of consumers, different versions of providers between CI and production. So we're in the process of deploying some features and some enhancements towards production, and we're going through the process. Now, what we might expect is that at each point within these environments, we'd make sure that the version of the consumer conforms to the contract or the expectations with respect to the provider on the same environment. So for example, if we were to run tests on the CI environment, we would expect to say, whats consumer v three works well and is compatible with provider v three? If those tests pass, and then if other tests pass, maybe they're security tests, performance tests, whatever the case may be, then maybe we have enough to satisfy the gate requirements in order to be able to move and promote towards the staging environment. However, one of the aspects of microservices and of course communication in general, is that we don't want to have to promote all components at the same time. So for instance, if I just wanted to promote consumer v three in this case towards managing, without promoting the provider v three towards staging, what else would I need to check? Well, we'd also need to check, of course, that the consumer v three works with the version of the provider which is in staging, which is provider v two. And then equally, if we wanted to promote consumer v. Two from staging towards production, we'd also have to make sure that that version of the consumer is compatible with the version of the provider that's already promoted or deployed towards production. And what if the inverse was the case? What if we wanted to promote the provider without promoting the consumer? Well then of course we'd have to make sure that the provider v three and CI is compatible with consumer v two in staging provider v two and staging is compatible with consumer v one in production and so on and so forth. So you can see how quickly the complexity can be increased. And then if you multiply this by more environments, by many more components, you will get to a situation that can be very difficult to manage. And one of the things that we really want to do is make sure that we still can keep the flexibility to allow us to promote hot fixes, bug fixes and feature extensions without having to promote all components and all of the dependencies in lockstep. And this is of course one of the main areas that contract testing and bi directional contract testing as an approach can come in to help. And that's what I'm going to jump into now. But first, let me just plant something in your mind. And it's a quote from my colleague Beth Scurry, who's one of the founders of Pact and Pactflow. And she says if you can't deploy services independently, then you don't have microservices. And not alone that you have a very costs approach to asserting quality and testing your implementations. And you have what we call a distributed monolith. And just in case the red text isn't kind of advertising it enough for you, that is a bad thing. And it removes the benefits that are promised by having an ecosystem of independent, verifiable and deployable microservices. So bi directional contract testing. So this is an approach that can really allow us to be able to safely evolve. And it's one whats also rewards an investment in a design first approach towards microservices and APIs. And it can help us avoid many of the pitfalls that we have discussed up until now. And if you're coming at this from, let's say, a familiarity with pacts or even maybe consumer driven contract testing, then this workflow is a little bit different. So it's schema based rather than a specification by example. As I mentioned, it really supports a design first provider workflow, so it's well suited to starting on the provider side. And it can be described as an ability to upgrade your existing tools and processes into a powerful contract testing solution without having to throw away investments that you've already made in your technical stack. So you can already leverage open API definitions, for example, for describing your API contracts on the consumer side, you can leverage your mocking tools like Cypress, like Wiremark, et cetera. And then to prove out that your API implementation matches that open API definition, you can leverage tools like ready API dread, rest assured postman, and so on and so forth. I would also say it's a more inclusive way of contract testing, whereas consumer driven contract testing really requires and mandates that you have access to the code because it fits more closely in with unit testing. But bi directional contract testing does not need direct access to the code, so it can support a wider demographic of contract testers. And when I refer to a pact in terms of contract testing, it means the artifact that is acting as the verifiable contract between a provider component and a consumer component. So I do not mean something like an open API definition which describes the entire surface area of an API. So what it does is by capturing the interaction expectations between the software component pairs. So just one integration at a time, we have an ability to independently verify the expectations. So this can happen on each side of a single integration, so we can disregard downstream dependencies and upstream dependencies, and we can just focus on one integration at a time so we don't have to stand up a complex environment for end to end testing. And the challenge of course that you might have experienced with certain mocking approaches or test doubles is you get stale assumptions quite quickly. And here your assumptions are kept in sync. So with the case of bi directional contract testing, the assumptions of the consumer will be validated to ensure that they are a subset of what the provider supports. And this is really good. And you might be familiar maybe with Joe Wallace, who's quite a famous tester and he always advantages for don't mock what you dont own because you will get drift quite quickly on those assumptions. And the verification here with bi directional contract testing can happen asynchronously, so one side does not need the other side to be available in order to verify. Once the comparisons on both sides happen, then you can be confidence that when these components communicate in real life, that all should be fine. And what I show here on the right hand side is an example of a packed JSON file. So it stores who the consumer is, who the provider is, the interactions that are expected between the consumer and the provider, and then some additional metadata. So you will also see some reference here to a packed specification. So there is a defined specification for describing the format and the structure of what should be contained within a packed JSON file. So how does all of this really work? Well, let's run through kind of an example here. So again, it starts on the provider side. Now theoretically it can start on both sides, hence the name bi directional, but it's well suited to starting on the provider side. So you can bring your API definition, like an open API definition. Once you can prove that the implementation of that API is matching what's specified on the ten, so to say, so you can bring your own provider tool to do that. Once you have those two things, you can publish that contract into the packed flow broker. Then on the consumer side you can continue to unit test as you will, you can bring your own mocking tool for testing. Of course you can use pacts DSL, but you're not mandated to. Once you serialize those unit tests or those expectations into a pact file, you can then present those to the broker. And once the broker chaos both sides of that, then the contract comparison or the cross contract verification is performed by the can I deploy tool, which is part of pact and packed flows. And we use this feature really to control and gate a release. So it ensures that what we're deploying is compatible with any integrations or any applications that depend on this component in the environment that we're targeting for the deployment. And zooming in on this can I deploy check how it makes that kind of gated decision is it uses whats we call the matrix of information. So it queries all versions of the API consumer, all versions of the API provider, all integrations between all versions, which ones are compatible with each other and then which environments they've been deployed to. So in a past life you might have relied on heavy processes to keep track of this information. Maybe we even had dedicated teams for this like configuration management or even change advisory boards. Now this is available instantaneous for you, so let's run through kind of a demo. So this would take too long with the actual pipeline. So I took some screenshots to speed it up a little bit. But what I've created is a provider API. It's written in. Net we have a products API which is just exposing some endpoints to allow us to retrieve some product information. I'm bringing my own functional testing tools schema thesis to test whats what I've implemented matches what I've designed on the consumer side of just a simple console net core app. And I'm going to again bring my own unit testing tool and mocking tool, this case wiremark. And I'm going to set the expectations. We're going to have some pipelines to publish my artifacts and both consumer pipeline and provider pipeline will determine if it's safe to make any of these changes independently. So here's my API definition. So it's open API document products API, three endpoints, get products, get products by id and then also an endpoint to delete products. So that's my design. Then once I implemented the code, I can some functional tests through schema thesis to provider that my implementation is indeed matching what's described in the design. Once that's caused, I can commit my code and then I can run my provider pipeline. So here's me running through just a GitHub action and then I'm leveraging the can I deploy check through the GitHub action for can I deploy to verify if it's safe for me to do this. This passes. Of course this is expected because it's the first time through the process. I don't have any consumer yet, so I'm not expecting to break any contract. So this gets published into Packflow. This is just an example of what it looks like in packflow. So we have three question marks for the consumer because we don't have one yet, we have the provider contract. And you can see also within Pactflow a representation of the open API definition as well. Jumping to consumer side, then I have my simple consumer app, and then I create my unit tests to verify the expectations. So again, nothing special here. I'm using X unit for unit testing, standard setup and arrange an act and an assert. I'm taking advantage here of the fact that Wiremark as a mocking tool has an actual integration with pact. So I'm leveraging that component and I'm serializing the expectations here into a pacts file. What gets serialized into a pacts file is the name of the consumer. It's all of the interaction expectations. So in this case I'm expecting to be able to call the products by id endpoint with a fictitious id. And if that happens, I'm expecting the provider to return HTTP 404 not found. And at the bottom you can see the name of the provider that I'm expecting to be able to honor this for me, again, here's a separate pipeline for the consumer. First time through the process, everything goes good. What I'm expecting and what I'm managing from the provider is indeed a valid subset of the open API definition. And here now we can see that the pact broker has been updated and everything is good. The consumer contracts and all of the assertions there are successful. Now what's going to happen is I'm going to make what would be regarded as a breaking change. So the product owner is going to come to me and say, frank, great job in deploying that API, but I see you have a delete endpoint there, and that really only should be for the admin API and not for the API that we're exposing towards external clients. So can you remove that endpoint now? For me that's a breaking change. I shouldn't be able to do that. I should version an API. But what pactflow and bi directional contract testing allows me to do is it allows me to make that change safely because I can fully understand and I have full visibility into how the consumer is expecting my API to provide for them so what their expectations are. And I know that they are not using the delete endpoint. So therefore it's safe for me to make what would traditionally be a breaking change. And you can see that the pact within the pacts broker between the provider and the consumer is still there, it's still valid. But you can see that the provider contract itself has updated and now it only has the two get endpoints. Now it's all well and good, seen all green. But what would happen if I was to try to make a change that would actually break my consumer client implementation? So here's another example of this. So the product owner has now asked me to make some changes because client errors 404 are hindering his reports and making him look bad. So he's asked me hey, can I change those 404s maybe into something different? I'm not going to debate it too much with him. What I'm going to do is check is this actually going to be a breaking change? And what I can do is make that change and instantaneously see that it would break the expectations on the consumer side because they are expecting a 404 to be returned. And if I try to change that, I will get notified that I cannot deploy this and it will prevent me from making this breaking change for the consumer. I'll see that in the CI process and then I'll also see it in Pactflow itself. And I can see that the contract comparison has failed and I see explicitly why it's failed. So the provider code no longer returns a 404 and that is expected by the consumer. So I get explicit information into that and the matrix of information that's continuously queried by the can I deploy check is also updated. And here we can see the top one has failed and we can see that the environment is in a so I was not allowed to deploy whats change into production. So hopefully you'll see that by introducing a technique like bi directional contract testing towards your microservices approach, it can reduce your need for end to end testing and integration testing and it can significantly rebalance your costs with regards to testing and enables you to really safely evolve microservices. You'll have an approach that's simpler, it's cheaper, it's faster, it scales linearly as your number of microservices scale, and it allows you to make sure that you can keep that flexibility and deploy independently. And with regards to how this benefits a design first approach one of the main challenges that we have with API design first is that you lose visibility quite quickly once you get an API into production and you start ramping up your number of consumers and it can be really hard to make informed decisions around what changes will be breaking, what changes will not be breaking. All you can do is assume that the full surface area of your API definition is deemed on by somebody but with contract testing it gives you that full explicit insight into how consumers are consumer your API and it lets you have a more pragmatic rather than dogmatic approach to your API versioning strategy. So hopefully you see the benefits. Hopefully you enjoyed my talk. Thanks very much for attending. If you want to connect and reach out please do. Here are my details and here's how you can get up and start playing with contract testing and also some useful resources. Whats helped me frame some of this talk. Thank you very much.
...

Frank Kilcommins

API Technical Evangelist @ SmartBear

Frank Kilcommins's LinkedIn account Frank Kilcommins's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways