-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[APM] Improve type safety across application boundaries #42252
[APM] Improve type safety across application boundaries #42252
Conversation
Pinging @elastic/apm-ui |
💔 Build Failed |
@@ -24,7 +24,7 @@ export async function loadTransactionList({ | |||
transactionType: string; | |||
uiFilters: UIFilters; | |||
}) { | |||
return await callApi<TransactionGroupListAPIResponse>({ | |||
return await callApi<typeof transactionGroupListRoute>({ |
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.
Instead of referring to a type I refer to a routing function.
Pretty stoked about this! I've been thinking about this as well, for instance the gap between Joi validation and query parameters feels brittle. I think we could have some kind of DSL that both TypeScript and Joi can handle, that would remove the need for at least some duplication. Another thing that I considered was to have static strings always for an API call, so return await callApi('/api/apm/transaction_groups/distribution', { }); Maybe a little overkill though 😅 |
💔 Build Failed |
Yes, there is definitely room for improvement. If I added all the things I wanted it would become a sinkhole :D I feel this is a pretty minimal first step, that will allow us to iterate on it. The typescript magic I added is still fairly simple. If we want to go more advanced we might want to look at typesafe-hapi so we don't duplicate that work. But that's a much bigger change if we were to convince Kibana to switch. |
That's an interesting thought. I definitely see the benefits of static endpoints. Currently our client side routes and server side APIs map almost 1:1 so we might want to change those as well for consistency. |
72871b0
to
7f7cfbc
Compare
💔 Build Failed |
💔 Build Failed |
e883295
to
8842233
Compare
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💚 Build Succeeded |
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.
LGTM, just a few nits/questions
|
||
export async function getJavaMetricsCharts(setup: Setup, serviceName: string) { | ||
const charts = await Promise.all([ | ||
const charts: GenericMetricsChart[] = await Promise.all([ |
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.
same here, curious why we need 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.
It was previously used here:
kibana/x-pack/legacy/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts
Lines 11 to 13 in 33c237e
export interface MetricsChartsByAgentAPIResponse { | |
charts: GenericMetricsChart[]; | |
} |
Removing it caused typescript errors - often this happens when a const like type: 'my-type'
is converted to type: string
. Re-adding GenericMetricsChart
solved 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.
If you find a way to remove it and still keep typescript happy I'll gladly do that.
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, removing this causes this error:
legacy/plugins/apm/public/hooks/useServiceMetricCharts.ts:14:3 - error TS2322: Type '[]' is not assignable to type '[{ title: string; key: string; yUnit: YUnit; totalHits: number; series: { title: string; key: string; type: ChartType; color: string; overallValue: number | null; data: { x: number; y: number | null; }[]; }[]; }, { ...; }, { ...; }, { ...; }, { ...; }] | [...]'.
Type '[]' is missing the following properties from type '[{ title: string; key: string; yUnit: YUnit; totalHits: number; series: { title: string; key: string; type: ChartType; color: string; overallValue: number | null; data: { x: number; y: number | null; }[]; }[]; }, { ...; }]': 0, 1
14 charts: []
~~~~~~
legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts:24:12
24 return { charts };
~~~~~~
The expected type comes from property 'charts' which is declared here on type '{ charts: [{ title: string; key: string; yUnit: YUnit; totalHits: number; series: { title: string; key: string; type: ChartType; color: string; overallValue: number | null; data: { x: number; y: number | null; }[]; }[]; }, { ...; }, { ...; }, { ...; }, { ...; }]; } | { ...; }'
legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts:13:1 - error TS6133: 'GenericMetricsChart' is declared but its value is never read.
13 import { GenericMetricsChart } from '../../transform_metrics_chart';
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
diff --git a/x-pack/legacy/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/legacy/plugins/apm/public/hooks/useServiceMetricCharts.ts
index 3480ede7ac..b1b0315b00 100644
--- a/x-pack/legacy/plugins/apm/public/hooks/useServiceMetricCharts.ts
+++ b/x-pack/legacy/plugins/apm/public/hooks/useServiceMetricCharts.ts
@@ -10,8 +10,10 @@ import { useUiFilters } from '../context/UrlParamsContext';
import { useFetcher } from './useFetcher';
import { PromiseReturnType } from '../../typings/common';
-const INITIAL_DATA: PromiseReturnType<typeof loadMetricsChartData> = {
- charts: []
+type MetricsChartDataResponse = PromiseReturnType<typeof loadMetricsChartData>;
+
+const INITIAL_DATA = {
+ charts: [] as Array<MetricsChartDataResponse['charts'][0]>
};
export function useServiceMetricCharts(
diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts
index 453ba614f2..f55d3320c7 100644
--- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts
@@ -7,13 +7,12 @@
import { Setup } from '../../helpers/setup_request';
import { getCPUChartData } from './shared/cpu';
import { getMemoryChartData } from './shared/memory';
-import { GenericMetricsChart } from '../transform_metrics_chart';
export async function getDefaultMetricsCharts(
setup: Setup,
serviceName: string
) {
- const charts: GenericMetricsChart[] = await Promise.all([
+ const charts = await Promise.all([
getCPUChartData(setup, serviceName),
getMemoryChartData(setup, serviceName)
]);
diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts
index 7ea1d0bf69..9fc9f3029d 100644
--- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts
@@ -10,10 +10,9 @@ import { getNonHeapMemoryChart } from './non_heap_memory';
import { getThreadCountChart } from './thread_count';
import { getCPUChartData } from '../shared/cpu';
import { getMemoryChartData } from '../shared/memory';
-import { GenericMetricsChart } from '../../transform_metrics_chart';
export async function getJavaMetricsCharts(setup: Setup, serviceName: string) {
- const charts: GenericMetricsChart[] = await Promise.all([
+ const charts = await Promise.all([
getCPUChartData(setup, serviceName),
getMemoryChartData(setup, serviceName),
getHeapMemoryChart(setup, serviceName),
x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts
Outdated
Show resolved
Hide resolved
}); | ||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1); |
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.
why are we removing this assertion?
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.
presumably because of 265cc3b?
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.
Yes, exactly. Removing defaultErrorHandler
was perhaps a bit out of the scope for this PR so I can re-add it if you think. Although I don't think it provided us with much (and now the status code is correctly 500 instead of 400).
💚 Build Succeeded |
Hey just for the record, @weltenwort and some of us on infra/logs are evaluating io-ts https://github.com/gcanti/io-ts to try to tackle this problem. See #41836 and #42356 for our first attempts to implement and see if it's worth the DSL. I like what you all have here too, it'd be great if we found a solution that we all used at least across o11y and maybe beyond! |
…nd-to-frontend-contract
💚 Build Succeeded |
Closing, work was done in #42961. |
This PR does two things
Problem:
We want to have type safety between the client and the server. We currently infer the response of a rest call by referring to the exported type on the server side:
There are are two problems with this:
SomeTypeFromBackend
is exported from deep inside the server application, and thus does take possible transformations at the routing layer into considerationSolution
Instead of referring to a specific return type the client should simply refer to the route method it is calling:
transaction_distribution_route
is the name of the route. This is at the very edge of the server side application so we can be much more confident that the return type of this method corresponds to what is actually transmitted over the wire.Additionally, since we are now passing the type of the backend route we can infer more than just the return type. Currently I've added inference of query params but in the future we might also be able to infer
method
and perhaps do some magic withpath
.I've only converted the
transaction_groups
APIs. I'd like to get feedback before converting the remaining APIs. I don't expect that to take more than half a day.