1

Say we have a CreditCardService microservice that depends on a ThreeDSecureService microservice, communicating using JSON.

Minor changes in the API (or even implementation) of the ThreeDSecureService could silently break the CreditCardService (and other potential clients). So, we would like automated tests.

I see two flawed approaches, and am wondering how to improve.

  1. Integration testing in ThreeDSecureService.Tests.

The accompanying test project of ThreeDSecureService could have an integration test with a fixed JSON input. Faking out any dependencies, it could run an otherwise complete call for that input, confirming that the service swallows the input.

The problem here is that if someone fails to realize how their changes could break clients, they are almost as likely to 'fix' the tests to match their changes.

  1. Integration testing in CreditCardService.Tests.

The client is the one that actually wants to test assertions about ThreeDSecureService's expected input. However, that would require the client solution to include the ThreeDSecureService project, as well as any projects it depends on. This would negate many of the advantages we get from using microservices!

How do we make assertions from the client (safeguarding the dependency) without breaking the loose coupling we get from using microservices?

Timo
  • 7,992
  • 4
  • 49
  • 67
  • 1
    Can't you simply have your integration test project know about them both (either by reference, or by starting them somehow)? The two services don't need to know about each other, but I'd see no harm in a test project referencing them both. – kkirk Aug 18 '17 at 08:38
  • Read my answer here: https://stackoverflow.com/a/45177810/569662. The OP had a similar question regarding how to manage service dependencies. – tom redfern Aug 18 '17 at 08:59
  • @kkirk But while you're working on the client project, you'll want to run regular tests. So you need the integration test project to be included in the client solution, no? It would be fine if we could reference the dependency's *DLL*... But how do we keep that up-to-date, i.e. latest and built? – Timo Aug 28 '17 at 14:46
  • @Timo I've tried to reply in a comment, but it always ends up way too long for a comment, so I'll just post an answer. – kkirk Aug 29 '17 at 08:48

2 Answers2

1

I hope I've understood what you asked, but if not that the following at least contributes something to your thinking.

The way I understand your question it is a very practical one, i.e. "How do I organize the solutions and projects to minimize the risk that a developer breaks a service contract without understanding the impact it would have on consumers of that service?"

In an ideal world, developers don't do that. If they make breaking changes to a service, they know they've done it, and it's a new version. That being said, I get your point - it's nice to have some sort of safeguard against that kind of human error since developers are only perfect most of the time.

If you're working in an environment, where you're creating microservices that other teams depend on and vice-versa, there's no way around a disciplined and structured approach, i.e. proper governance. A full test environment that mirrors production, where all services are running and you can test that your service integrates well with the services it depends on doesn't hurt either.

If you're in control of all the services that need to communicate with each other, there's (at least in my opinion) no reason why you shouldn't have a single solution where all your service projects are included and where your integration test project starts all the services it needs to and checks the integrations between them are working as expected.

Your individual microservices shouldn't know anything about the outside world, apart from contracts/interfaces, and you should have Unit Tests that test every microservice in isolation, mocking out dependencies.

If one solution becomes too large, you could break it into smaller pieces, to one solution for each service that includes the service and its nearest neighbours. In your example, that could be a solution that contains ThreeDSecureService, CreditCardService and CreditCardService.IntegrationTests (which tests CreditCardService which in turn invokes ThreeDSecureService). That way, when a developer is working on ThreeDSecureService, the integration tests will let the developer know that he/she has broken the integration. That still requires the developer to remember to actually run the Integration tests of course.

The end goal as I see it, is to incorporate a gate into your build flow, that ensures that developers are not able to publish changes that would cause integration tests to fail, coupled with automatic deployment. That will ensure that basic integration errors do not make it into production.

kkirk
  • 352
  • 1
  • 7
  • I enjoyed reading this. :) How would you recommend that the microservices "know" the contracts? Currently, they have their own models that they serialize, *assuming* that the service they are calling still honors that contract. **My goal is to have an early warning if those contracts change** (which usually means somebody messed up). The contracts should be easy to maintain, and add little overhead in terms of solution management. – Timo Aug 29 '17 at 12:36
  • Great, I'm happy it was useful. I'd recommend having interfaces defining the contracts in a NuGet package or similar, which is versioned so that you know exactly which contract each service implements. I'd handle versioning of services by defining a separate namespace for each service, i.e. CreditCardService.v1_0, CreditCardService.v1_1 etc. You could do the same for data models in a separate NuGet package, which the contract NuGet package depends on. This way, when all services are on the same version of the packages, they have the same exact understanding of each data model. – kkirk Aug 29 '17 at 13:00
  • Interesting idea. I'm also considering similar alternatives, such as referencing DLLs with just the contracts, or having one big Contracts project (stuffed with interfaces) referenced by everything. I'll have to investigate the pros and cons of each. – Timo Aug 29 '17 at 15:06
0

To avoid breaking integration, use public contracts. For example, RAML. Describe models that clients of particular services would get and pass, describe actions and parameters. In this case, nobody depends on particular service or implementation, both sides need to just test their communication with contracts.

Additional safety measure would be publishing your contract as dll. In this case, consumers of a service would notice changes in a minute. And if they have unit tests, they will probably show any problems more earlier.

As a main rule to stay decoupled: don't reference implementation, reference contracts instead.

cassandrad
  • 3,412
  • 26
  • 50