[APM] Improve type safety across application boundaries#42252
[APM] Improve type safety across application boundaries#42252sorenlouv wants to merge 13 commits intoelastic:masterfrom
Conversation
|
Pinging @elastic/apm-ui |
💔 Build Failed |
There was a problem hiding this comment.
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 |
dgieselaar
left a comment
There was a problem hiding this comment.
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.
same here, curious why we need it
There was a problem hiding this comment.
It was previously used here:
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.
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.
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.
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
| isBoom: true | ||
| message: 'request failed' | ||
| }); | ||
| expect(consoleErrorSpy).toHaveBeenCalledTimes(1); |
There was a problem hiding this comment.
why are we removing this assertion?
There was a problem hiding this comment.
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:
SomeTypeFromBackendis 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_routeis 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
methodand perhaps do some magic withpath.I've only converted the
transaction_groupsAPIs. I'd like to get feedback before converting the remaining APIs. I don't expect that to take more than half a day.