Microservice Architecture has been a hot buzz word for a while,
but good concrete examples are still so lack.
I thought it might helps to give a brief review for my several days’ work.
Problem I Met
Three systems involve into a game of data passing.
My silent spiders, controlled by System
Spiderman, will crawl
information through the great internet.
Manhattan will gather working achievement produced by spiders and
my lovely workmates.
These results of work will reflect to System
The picture below, with a boundary around
Manhattan, describes a general view of these systems:
Aha, you got it, I need to design and implement system
Manhattan from scratch.
Design Flow, Make RESTful Contract
It’s hard to guess the behavior other components will act if we chose not to constraint them, since they’re self-independence.
A well designed and implemented contract determines how these systems interact with each other, and in which way we will program.
Microservices are often integrated using REST over HTTP.
Representational State Transfer, aka RESTful, is a software architecture style consisting of guidelines and best practices for creating good contract.
So I design data flow as below:
Calls crossover boundaries are via RESTful HTTP requsts.
Cambridge offering an API
It has some predictable behaviors:
- Response 204 if set success;
- Response 403 if auth fail;
- Response 404 if set fail;
- Other unknown error.
try: url = make_url(type, id) data = make_data(price, inventory) resp = requests.post(url, data=data) status = resp.status_code if status == 204: upload.set_upload_success() elif status == 403: raise NoAccess(url) elif status == 404: upload.set_not_upload_anymore() db.session.delete(binding) else: raise UnknownError(url, resp) except requests.ConnectionError: sentry.captureException()
Test based on contract
Contract tests : verify interactions at the boundary of an external service asserting that it meets the contract expected by a consuming service.
Connections out to external services require special attention since they cross network boundaries. The system should be resilient to outages of remote components. Gateways contain logic to handle such error cases.
Typically, communications with external services are more coarse grained than the equivalent in process communications to prevent API chattiness and latency.2
Note: Mock library make contract test easier. Here I simply stub requests call.
@pytest.fixture def requests(monkeypatch): req = Mock() monkeypatch.setattr(upload_sale, 'requests', req) return req def failure_case_on_meeting_error(requests): requests.post = Mock(side_effect=Exception) def failure_case_on_response_403(requests): requests.post.return_value.status_code = 403 @pytest.mark.parametrize('case', [ failure_case_on_response_403, failure_case_on_meeting_error, ]) def test_product_upload_failed(product, binding, requests, case): case(requests) upload_sale.handle_product(product) service = ProductService.get(product.id) assert service.product.upload_status == UploadStatus.FAILURE assert service.product.upload_count == 1 assert service.product.uploaded_at def test_product_upload_success(product, binding, requests): requests.post.return_value.status_code = 204 upload_sale.handle_product(product) service = ProductService.get(product.id) assert service.product.upload_status == UploadStatus.SUCCESS def test_product_upload_no_longer(product, binding, requests): requests.post.return_value.status_code = 404 upload_sale.handle_product(product) service = ProductService.get(product.id) assert service.product.upload_status == UploadStatus.DEPRECATE # set deprecate assert service.product.upload_count == 1 assert service.product.uploaded_at assert not AppBindingService.get_bindings(product.id) # no bindings now.
- Contract is probably one of the most important thing in microservice architecture.
- Design a good contract, just follow widely-used RESTful API-style.
- Tightly follow the contract you’ve just made in programming.
- Contract Driven Test