Как связать модель с отношением «есть один» в интерфейсе GraphQL - PullRequest
1 голос
/ 02 мая 2019

У меня есть сервер GraphQL / Apollo, использующий Sequelize / mysql. Мои типы GraphQL (Employee, Contractor) каждый реализуют интерфейс Person. Моя модель базы данных содержит таблицу сотрудника, подрядчика и событий. Я бы хотел, чтобы у моего Типа личности было «много» отношений с Событиями. В то время как мой тип события "принадлежит" к типу личности - сотрудник или подрядчик.

Я предполагаю, что это как-то связано с полем person_id в Типе события. Я могу заставить его работать без интерфейса на одной таблице «Сотрудник» и изменить person_id на employee_id. Так что я предполагаю, что он просто не знает, как провести различие между Сотрудником и Подрядчиком для ссылки на эту таблицу?

//typeDefs.js

const typeDefs = gql`
  type Event {
    id: ID!
    person_id: ID!
    location: String!
    escort: String!
  }

  interface Person {
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
  }

  type Employee implements Person {
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
    employee_sh_id: String
  }

  type Contractor implements Person {
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
    escort_required: Boolean!
  }
//Employee model

module.exports = (sequelize, DataTypes) => {
  const Employee = sequelize.define('Employee', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    first_name: DataTypes.STRING,
    last_name: DataTypes.STRING,
    department_company: DataTypes.STRING,
    emplyee_sh_id: DataTypes.STRING
  }, {});
  Employee.associate = function(models) {
      Employee.hasMany(models.Event);
  };
  return Employee;
};
// Contractor model

module.exports = (sequelize, DataTypes) => {
  const Contractor = sequelize.define('Contractor', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    first_name: DataTypes.STRING,
    last_name: DataTypes.STRING,
    department_company: DataTypes.STRING,
    escort_required: DataTypes.BOOLEAN,
  }, {});
  Contractor.associate = function(models) {
      Contractor.hasMany(models.Event);
  };
  return Contractor;
};
// Event model

module.exports = (sequelize, DataTypes) => {
  const Event = sequelize.define(
    "Event",
    {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      person_id: DataTypes.INTEGER,
      location: DataTypes.STRING,
      escort: DataTypes.STRING
    },
    {}
  );
  Event.associate = function(models) {
    Event.belongsTo(models.Employee),
    Event.belongsTo(models.Contractor)
  };
  return Event;
};
// resolvers.js

const resolvers = {
  Query: {
    async employee(root, { id }, { models }) {
      return models.Employee.findByPk(id);
    },
    async contractor(root, { id }, { models }) {
      return models.Contractor.findByPk(id);
    },
    async employees(root, args, { models }) {
      return models.Employee.findAll();
    },
    async contractors(root, args, { models }) {
      return models.Contractor.findAll();
    },
    async event(root, { id }, { models }) {
      return models.Event.findByPk(id);
    },
    async events(root, args, { models }) {
      return models.Event.findAll();
    }
  },

  Mutation: {
    async addEmployee(
      root,
      {
        first_name,
        last_name,
        department_company,
        employee_sh_id
      },
      { models }
    ) {
      return models.Employee.create({
        first_name,
        last_name,
        department_company,
        employee_sh_id
      });
    },

    async addContractor(
      root,
      {
        first_name,
        last_name,
        department_company,
        escort_required,
      },
      { models }
    ) {
      return models.Contractor.create({
        first_name,
        last_name,
        department_company,
        escort_required,
      });
    },

    async addEvent(
      root,
      { person_id, location, escort },
      { models }
    ) {
      return models.Event.create({
        person_id,
        location,
        escort
      });
    },

  Person: {
    __resolveType: person => {
      if (person.employee) {
        return "Employee";
      }
      return "Contractor";
    }
  },

  Employee: {
    events: (parent, args, context, info) => parent.getEvents(),
  },

  Contractor: {
    events: (parent, args, context, info) => parent.getEvents(),
  }
};

1 Ответ

1 голос
/ 02 мая 2019

Вам вообще нужен интерфейс?

Основная цель, стоящая за абстрактными типами, такими как интерфейсы и объединения, состоит в том, что они позволяют определенному полю разрешать один из набора типов. Если у вас есть типы Contractor и Employee и вы хотите, чтобы определенное поле возвращало любой тип, имеет смысл добавить интерфейс или объединение для обработки этого сценария:

type Event {
  contractors: [Contractor!]!
  employees: [Employee!]!
  people: [Person!]! # could be either
}

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

Серебряной пули нет

Работа с наследованием в реляционных базах данных может быть хитрой , и нет единого универсального ответа. При использовании Sequelize или другого ORM дела обстоят даже странно, потому что ваше решение должно работать и в рамках этой конкретной библиотеки. Вот несколько разных способов решения этой проблемы, хотя это далеко не полный список:

  • Если у вас есть только несколько полей, которые возвращают тип Person, вы можете обойтись без отдельных таблиц и отдельных моделей и просто объединить результаты самостоятельно. Что-то вроде:
people: async (event) => {
  const [employees, contractors] = await Promise.all([
    event.getEmployees(),
    event.getContractors(),
  ])
  const people = employees.concat(contractors)
  // Sort here if needed
  return people
}

Это означает, что вы теперь запрашиваете БД дважды и, возможно, тратите дополнительное время на сортировку, которую ББ в противном случае сделала бы для вас. Тем не менее, это означает, что вы можете вести отдельные таблицы и модели для Подрядчиков и Сотрудников, что означает, что запрашивать эти объекты просто.

  • Объедините подрядчиков и сотрудников в одну таблицу, используя какое-то поле type, чтобы различать их. Затем вы можете использовать области действия , чтобы помочь вам правильно смоделировать отношения в Sequelize:
Event.hasMany(models.Person, { as: 'employees', scope: {type: 'EMPLOYEE'} ... })
Event.hasMany(models.Person, { as: 'contractors', scope: {type: 'CONTRACTOR'} ... })
Event.hasMany(models.Person, { as: 'people', /** no scope **/ ... })

Это работает, даже если не кажется "правильным" иметь все в одной таблице. Вы должны помнить, чтобы правильно охватить свои ассоциации и запросы.

  • Если вы используете Sequelize строго как ORM и не генерируете базу данных из ваших моделей Sequelize (т.е. не вызываете sync), также возможно смоделировать view как модель Sequelize. Представления немного трудны для написания и поддержки, но это позволит вам хранить отдельные таблицы для сотрудников и подрядчиков при создании виртуальной таблицы из двух других, которые можно использовать для запроса всех сотрудников.
...