Переопределение значения корневого объекта типа GraphQL Union / Interface

У меня есть служба Apollo GraphQL, которая делегирует внутреннюю службу gRPC. У этой службы есть конечная точка, которая возвращает сообщение, содержащее oneof, которое я сопоставляю с Union в GraphQL.

Это просто, но при реализации резолверов используется изрядная доля шаблонов. Предположим, у меня есть следующее определение сообщения protobuf:

message MyUnionMessage {
  oneof value {
    UnionType1 type1 = 1;
    UnionType1 type2 = 3;
    UnionType1 type3 = 4;
  }
}
message UnionType1 {<type 1 props>}
message UnionType2 {<type 2 props>}
message UnionType3 {<type 3 props>}

Моя соответствующая схема GraphQL выглядит примерно так:

union MyUnionType = UnionType1 | UnionType2 | UnionType3
type UnionType1 {<type 1 props>}
type UnionType1 {<type 2 props>}
type UnionType1 {<type 3 props>}

В привязке javascript для gRPC объект MyUnionMessage будет иметь два свойства: value, который представляет собой строку, указывающую, какой тип значения содержится, и свойство, названное для этого типа. Итак, если бы у меня был MyUnionMessage, содержащий, например, UnionType2, объект выглядел бы так:

{
  value: 'type2',
  type2: {...}
}

Это удобно для реализации __resolveType, поскольку я могу сделать простой switch для значения в value, но затем мне нужно написать преобразователь для всех полей всех конкретных типов.

Я ищу возможность сделать что-то вроде этого:

resolvers = {
  MyUnionType: {
    __resolveType(obj) {
      switch(obj.value) {
      case 'type1': return 'UnionType1';
      case 'type2': return 'UnionType2';
      case 'type3': return 'UnionType3';
      default: return null;
    },
    __resolveValue(obj) {
      return obj[obj.value];
    },
  },
};

По сути, я хочу написать «преобразователь» на уровне общего типа объединения (или интерфейса), который преобразует объект до того, как он будет передан конкретному преобразователю.

Возможно ли такое?


person Kris Pruden    schedule 10.01.2019    source источник


Ответы (1)


Держу пари, что такой сценарий обычно решается путем преобразования данных до того, как они попадут в логику __resolveType. Например, предположим, что у вас есть поле Query, которое вернуло список из MyUnionType. Ваш преобразователь для этого поля может выглядеть примерно так:

function resolve (arr) {
  return arr.map(obj => {
    return {
      ...obj[obj.value]
      type: obj.value // or whatever field name that won't cause a collision
    }
  })
}

Затем вы switch на type внутри __resolveType, и все готово. Конечно, это означает, что если у вас есть несколько полей, возвращающих MyUnionType, вы захотите извлечь эту логику в служебную функцию, которая может использоваться каждым преобразователем.

Я не думаю, что на самом деле нет способа делать то, что вы пытаетесь сделать с существующим API. Конечно, можно было бы сделать что-то вроде этого:

const getUnionType(obj) {
  switch(obj.value) {
    case 'type1': return 'UnionType1';
    case 'type2': return 'UnionType2';
    case 'type3': return 'UnionType3';
    default: {
      throw new Error(`Unrecognized type ${obj.value}`)
    }
  }
}

const resolvers = {
  MyUnionType: {
    __resolveType(obj) {
      const type = getUnionType(obj)
      Object.assign(obj, obj[obj.value])
      return type
    },
  },
};

Это работает, но имейте в виду, что это немного хрупко, поскольку предполагает, что resolveType всегда будет получать то же значение корня, что и функция resolve, которое гипотетически может измениться в будущем.

person Daniel Rearden    schedule 10.01.2019
comment
Я так и подозревал. Уловка Object.assign действительно работает, так что я пока этим воспользуюсь. Если реализация apollo в какой-то момент изменится так, что это больше не работает, я разберусь с этим :) Спасибо! - person Kris Pruden; 11.01.2019