From 33a191d3c4fa777b874f1a906fd9078e21be9e33 Mon Sep 17 00:00:00 2001 From: Carlos Cortizas <97907068+CarlosCortizasCT@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:31:11 +0100 Subject: [PATCH] Update data models guidelines (#724) * docs: update data models guidelines * docs: add code snippet to data models creation docs * docs: typo Co-authored-by: Douglas Egiemeh --------- Co-authored-by: Douglas Egiemeh --- docs/guidelines/creating-new-model.md | 130 ++++++++++++++------ docs/guidelines/migrating-existing-model.md | 18 +-- 2 files changed, 93 insertions(+), 55 deletions(-) diff --git a/docs/guidelines/creating-new-model.md b/docs/guidelines/creating-new-model.md index 72141df69..7d69e5a5f 100644 --- a/docs/guidelines/creating-new-model.md +++ b/docs/guidelines/creating-new-model.md @@ -23,46 +23,17 @@ Here's an example of such a file: ```ts import type { Channel, ChannelDraft } from '@commercetools/platform-sdk'; -import type { - TClientLoggingGraphql, - TGeometryGraphql, - TGeometryRest, - TLocalizedStringDraftGraphql, - TLocalizedStringGraphql, -} from '@commercetools-test-data/commons'; import type { TBuilder } from '@commercetools-test-data/core'; +import { + TCtpChannel, + TCtpChannelDraft, +} from '@commercetools-test-data/graphql-types'; -export type TChannel = Channel; -export type TChannelRest = Omit & { - geoLocation?: TGeometryRest; -}; -export type TChannelGraphql = Omit< - Channel, - // In GraphQL, these properties have different shapes. - 'name' | 'description' | 'createdBy' | 'lastModifiedBy' | 'geoLocation' -> & { - __typename: 'Channel'; - createdBy?: TClientLoggingGraphql; - lastModifiedBy?: TClientLoggingGraphql; - name?: string; - nameAllLocales?: TLocalizedStringGraphql | null; - description?: string; - descriptionAllLocales?: TLocalizedStringGraphql | null; - geoLocation?: TGeometryGraphql; -}; +export type TChannelRest = Channel; +export type TChannelGraphql = TCtpChannel; -export type TChannelDraftRest = Omit & { - geoLocation?: TGeometryRest; -}; -export type TChannelDraftGraphql = Omit< - ChannelDraft, - 'name' | 'description' | 'geoLocation' -> & { - name?: TLocalizedStringDraftGraphql; - description?: TLocalizedStringDraftGraphql; - geoLocation?: TGeometryGraphql; - __typename: 'ChannelDraft'; -}; +export type TChannelDraftRest = ChannelDraft; +export type TChannelDraftGraphql = TCtpChannelDraft; export type TCreateChannelBuilder< TChannelModel extends @@ -75,10 +46,90 @@ export type TCreateChannelBuilder< As you can see, we have four main types: two for the full entity (`TChannelRest` and `TChannelGraphql`) and another pair for the draft version (`TChannelDraftRest` and `TChannelDraftGraphql`). -The REST representations are just using the `@commercetools/platform-sdk`. +The REST representations are just using the `@commercetools/platform-sdk` package. This is a package which is auto-generated with the latest versions of the commercetools REST APIs and already contains all the Typescript interfaces describing the models. -On the other hand, the GraphQL representation can use the previous ones as a blueprint, but most likely will use different types for some of their properties (specially the nested models). +For GraphQL, we use the `@commercetools-test-data/graphql-types` package. +This is an internal package of this repository and it exposes the types for the different services we might want to use models from: `core` (organization related), `ctp` (project related), `mc` (MC Gateway) and `settings` (MC Settings). +You might want to run the `generate-types` NPM script to make sure the package types are updated. + +### Handling references + +Something relevant to keep in mind is that reference properties are handled differently between the REST and GraphQL APIs. + +References are the way our APIs can link entities with each other but the design is a little different. + +In the REST APIs, whenever we want to link entities chances are we don't want to bring the linked entity when we load the main entity we want to use. For this reason, referenced entity objects looks like this: + +```ts +export interface StoreReference { + readonly typeId: 'store'; + readonly id: string; + readonly obj?: Store; +} + +// Usage example +interface Cart extends BaseResource { + readonly id: string; + ... + readonly store?: StoreReference; +} +``` + +As you can see, the object by default only loads the ID of the referenced entity but it also has an `obj` optional property with the actual linked entity object you can optionally load (through endpoint parameters). + +Since GraphQL implements a query language where consumers can define what they want to consume, the way those types are defined is different. +In this context, what was decided is to actually have the property defined as the linked entity but also have a reference property similar: + +```ts +type TCtpReference = { + __typename?: 'KeyReference'; + key: TCtpScalars['String']['output']; + typeId: TCtpScalars['String']['output']; +}; + +// Usage example +type TCtpCart = TCtpReferenceExpandable & + TCtpVersioned & { + __typename?: 'Cart'; + id: TCtpScalars['String']['output']; + ... + store?: TCtpMaybe; + storeRef?: TCtpMaybe; + }; +``` + +You need to bear this in mind when configuring the REST and GraphQL fields of a test data model since the value you will use for populating the referenced entity property will be different among REST and GraphQL specific configuration (and the latter has one extra property compared with the former). + +Here is an example: + +```ts +export const restFieldsConfig: TModelFieldsConfig = { + fields: { + ...commonFieldsConfig, + store: fake(() => Reference.presets.storeReference()), + }, +}; +export const graphqlFieldsConfig: TModelFieldsConfig = { + fields: { + ...commonFieldsConfig, + __typename: 'InventoryEntry', + store: fake(() => StoreGraphql.random()), + storeRef: fake((f) => Reference.presets.storeReference()), + }, + postBuild: (model) => { + const storeRef = model.store + ? Reference.presets + .channelReference() + .id(model.store.id) + .buildGraphql>() + : null; + return { + storeRef, + }; + }, +}; +``` ## Configuring fields @@ -119,7 +170,6 @@ const commonFieldsConfig = { address: fake(() => Address.random()), reviewRatingStatistics: null, custom: null, - geoLocation: null, }; export const restFieldsConfig: TModelFieldsConfig = { fields: { diff --git a/docs/guidelines/migrating-existing-model.md b/docs/guidelines/migrating-existing-model.md index 8c7436a38..b884d4aa5 100644 --- a/docs/guidelines/migrating-existing-model.md +++ b/docs/guidelines/migrating-existing-model.md @@ -36,24 +36,12 @@ In the legacy data models we were supporting three representations of a data mod ```ts import type { Channel } from '@commercetools/platform-sdk'; -import type { - TClientLoggingGraphql, - TLocalizedStringDraftGraphql, - TLocalizedStringGraphql, -} from '@commercetools-test-data/commons'; +import type { TBuilder } from '@commercetools-test-data/core'; +import { TCtpChannel } from '@commercetools-test-data/graphql-types'; export type TChannel = Channel; export type TChannelRest = Channel; -export type TChannelGraphql = Omit< - TChannel, - 'name' | 'description' | 'createdBy' | 'lastModifiedBy' -> & { - __typename: 'Channel'; - createdBy: TClientLoggingGraphql; - lastModifiedBy: TClientLoggingGraphql; - nameAllLocales?: TLocalizedStringGraphql | null; - descriptionAllLocales?: TLocalizedStringGraphql | null; -}; +export type TChannelGraphql = TCtpChannel; ``` In the new implementation patterns, we don't want to keep three representations of the data model so the first one (`TChannel` in this case) should no longer be used. You can see that it is the same as the `TChannelRest` type.