-
Notifications
You must be signed in to change notification settings - Fork 398
Transports: DRY HTTP Transport & Client classes #5099
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
base: master
Are you sure you want to change the base?
Conversation
|
Thank you for updating Change log entry section 👏 Visited at: 2025-12-02 22:11:17 UTC |
Typing analysisNote: Ignored files are excluded from the next sections.
|
|
✅ Tests 🎉 All green!❄️ No new flaky tests detected 🔗 Commit SHA: c87e32b | Docs | Datadog PR Page | Was this helpful? Give us feedback! |
BenchmarksBenchmark execution time: 2025-12-03 20:38:20 Comparing candidate commit c87e32b in PR branch Found 2 performance improvements and 1 performance regressions! Performance is the same for 41 metrics, 2 unstable metrics. scenario:profiling - Allocations (profiling disabled)
scenario:profiling - Allocations (profiling enabled)
scenario:profiling - intern mixed existing and new
|
ac5f229 to
3108b35
Compare
8ba9902 to
f3dc3d2
Compare
| private | ||
|
|
||
| def send_request(request, &block) | ||
| def send_request_impl(request, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love a better name for this method but can't think of one at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR changed every send_request to not pass in a block. Being that the case, why not merge these two and make send_request directly trigger the public_send?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this change and tests majorly broke because they do a lot of mocking. I do like the change, I suppose need to spend more time rewriting the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... somehow not surprised. This is why I mentioned
TBH I suspect in the long run it may be easier to start from scratch and rebuilding the support for the required feature-set VS slowly untangling the current mess back into a good design step by step.
I believe this design is at this point so weird that "chipping" at it is incredibly hard.
ivoanjo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking this up! Left a few notes.
TBH I suspect in the long run it may be easier to start from scratch and rebuilding the support for the required feature-set VS slowly untangling the current mess back into a good design step by step.
But having said that, I'll take any improvement I can get.
| # Raised when configured with an unknown API version | ||
| class UnknownApiVersionError < StandardError | ||
| attr_reader :version | ||
|
|
||
| def initialize(version) | ||
| super | ||
|
|
||
| @version = version | ||
| end | ||
|
|
||
| def message | ||
| "No matching transport API for version #{version}!" | ||
| end | ||
| end | ||
|
|
||
| # Raised when the API verson to downgrade to does not map to a | ||
| # defined API. | ||
| class NoDowngradeAvailableError < StandardError | ||
| attr_reader :version | ||
|
|
||
| def initialize(version) | ||
| super | ||
|
|
||
| @version = version | ||
| end | ||
|
|
||
| def message | ||
| "No downgrade from transport API version #{version} is available!" | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I realize this already partially existed before.
Having said that -- these two situations only happen when we have a bug in our code that can't be recovered from, and we don't particularly have a need to access the version or to recover from the individual errors.
So my suggestion is -- let's get rid of these and replace them with raise ArgumentError, "Unknown API version: #{api_id}" (or similar) below. I think that saves us some code and we can always introduce the more specific classes if we ever need to do anything with them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do this, I don't think I'm strongly attached to the existing exceptions.
| class << self | ||
| # The HTTP client class to use for requests, derived from | ||
| # Core::Transport::HTTP::Client. | ||
| # | ||
| # Important: this attribute is NOT inherited by derived classes - | ||
| # it must be set by every Transport class that wants to have a | ||
| # non-default HTTP::Client instance. | ||
| attr_accessor :http_client_class | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This... is a kinda weird pattern. May I suggest making it an argument that gets passed to the constructor, or a regular method that needs to be overwritten by subclasses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking in general was to make the transports more declarative but I can try a version with an instance method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think declarative sometimes works really well...
In our case, I think it's made it really hard to reason and fix things with the current transport design. My thinking is that at this point we should strip out as much as possible of it until we get to a better state. Then, once we're happier with the transport design, we can reintroduce the declarative features as a nicer DSL for underpinnings we're happy with, and it won't affect with the overall design we're trying to fix.
|
|
||
| # Base class for transports. | ||
| class Transport | ||
| attr_reader :client, :apis, :default_api, :current_api_id, :logger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: I think some (all?) of these can be made private
| private | ||
|
|
||
| def send_request(request, &block) | ||
| def send_request_impl(request, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR changed every send_request to not pass in a block. Being that the case, why not merge these two and make send_request directly trigger the public_send?
| shared_context 'APIs with fallbacks' do | ||
| let(:current_api_id) { :v2 } | ||
| let(:apis) do | ||
| Datadog::Core::Transport::HTTP::API::Map[ | ||
| v2: api_v2, | ||
| v1: api_v1 | ||
| ].with_fallbacks(v2: :v1) | ||
| end | ||
|
|
||
| let(:api_v1) { instance_double(Datadog::Core::Transport::HTTP::API::Instance, 'v1', encoder: encoder_v1) } | ||
| let(:api_v2) { instance_double(Datadog::Core::Transport::HTTP::API::Instance, 'v2', encoder: encoder_v2) } | ||
| let(:encoder_v1) { instance_double(Datadog::Core::Encoding::Encoder, 'v1', content_type: 'text/plain') } | ||
| let(:encoder_v2) { instance_double(Datadog::Core::Encoding::Encoder, 'v2', content_type: 'text/csv') } | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Is it me or does every entry in this spec include this shared_context? E.g. I think we can simplify it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably due to copy-pasting from the tracing test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I think we overuse shared_contexts sometimes, so I'm always on the look out for simplifying them)
| describe '#initialize' do | ||
| include_context 'APIs with fallbacks' | ||
|
|
||
| it { is_expected.to have_attributes(apis: apis, current_api_id: current_api_id) } | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Maybe remove this? This test is trivial and this behavior ends up being exercised separately by the other tests
What does this PR do?
Removes duplicated code from Transport classes and deletes most of the HTTP::Client classes.
This PR continues on the path started in #5095.
Most of the HTTP::Client classes were functionally identical, except for the Tracing::Traces one which had additional logic for API version downgrades. The difference between the Client classes was in
send_XXX_payloadmethods which were largely identical. They have been replaced by a singlesend_requestmethod with the XXX as the argument. Then, all of the HTTP::Client classes have been removed except for the Tracing::Traces one which derives from the core HTTP::Client.This PR moves also moves the downgrading methods to the core class because they will be used by DI in the next PR. Accordingly, some unit tests have been moved to core. Not all because I think there needs to be additional work to have mock APIs, since existing unit tests under Tracing::Traces utilize the APIs in Traces.
Finally, a base Transport class in core has been created with the API management code that was identical across all of the Transport classes in the various components. The per-component Transport classes remain since they still have unique methods but these now all derive from the core base class.
Motivation:
Making transport layer more legible to implement API downgrades for DI
Change log entry
None
Additional Notes:
How to test the change?
Existing tests