-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A spec-backed protocol for inter-service communication #9
Comments
I see the appeal of this approach. I have one minor challenge regarding this approach, and that is that this approach seems to be Clojure oriented, in that it is heavily relying on namespace support of keywords and spec. Not saying that this is a problem, however, ideally we would like to have the same advantages for our Ruby consumers. Any thoughts on that? For reference, at the moment we use https://github.com/nedap/postman/blob/master/bin/generators/ruby_generator.rb (which uses xsd2ruby under the hood) to build Plain Old Ruby Objects for the different messages we have defined using an XSD (see https://github.com/nedap/postman and https://github.com/nedap/postman/blob/master/models/Message.xsd). Do you see any possibilities to generate the same using the approach you described for how to best deal with our messaging formats? I know it is a lot to ask, however, I would like to think of an approach to migrate our current Postman messages to incorporate some of the ideas you mention as well, so consider these questions more as a hammock-time-trigger :) |
Yes. I'd keep using XSD in a quite similar manner, while adding:
That versioning could be achieved either using some or other official XSD feature, or in an ad-hoc manner by convention. Out of this XSD, we'd emit both Ruby classes, and Clojure specs.
So, XSD remains canonical, while the goals of this issue seem still met. One also could do it in the opposite direction (making specs canonical, emit Ruby out of those), but it seems an approach bound to give issues. |
Brief
Given a microservices architecture where services communicate via e.g. Kafka, I advocate that those Kafka messages should be:
clojure.spec
)(spec/def :messaging.blue-app.v1/age integer?)
(spec/def :messaging.blue-app.v1/user (spec/keys :req [:messaging.blue-app.v1/age]))
Rationale
Specs
The usual reasons apply: contracts reduce the chance for misunderstandings, which often translate into bugs.
This particularly applies to ever-evolving, distributed systems.
Namespaces
I advocate that both message names, and those messages' attributes are heavily namespaced.
clojure.spec
ingVersioning
As part of the namespacing strategy, version numbers should be part of each message/attribute namespace.
An additional benefit is that unit-testing has guaranteed correctness:
How
Monotonically increasing, natural version numbers
i.e v1 -> v2 -> v3 -> ...
Message specs are decoupled from model specs
(spec/def :blue-app/user ...)
spec serving as a 'write model', i.e. a schema you follow before persisting to a DB(spec/def :messaging.blue-app.v1/user)
one should not leverage the:blue-app/user
spec at allImmutable, centralized message schemas
Closing thoughts
Specs in production
It would be unfortunate to do all this effort, and then disable spec checking in production.
I have created nedap/speced.def#70 accordingly.
Zero-downtime + exactly-once processing
A usual problem in event-driven architectures is:
With my recipe, this problem disappears altogether:
production.edn
) to only process a finite, known set of message versions#{:messaging.blue-app.v1/user}
#{:messaging.blue-app.v2/user}
The text was updated successfully, but these errors were encountered: