Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Looking for the right tech stack for GraphQL application

Looking for the right tech stack for GraphQL application

Nikita Galkin

March 16, 2019
Tweet

More Decks by Nikita Galkin

Other Decks in Programming

Transcript

  1. Nikita Galkin Love and Know: ▰ How to make developers

    and business happy ▰ Technical and process debt elimination Believe that: ▰ Any problem must be solved at the right level ▰ Software is easy. People are hard ▰ A problem should be highlighted, an idea should be "sold", a solution should be demonstrated Links: Site GitHub Twitter Facebook 2
  2. GraphQL is an open source query language created by Facebook

    The specification published in June 2018
  3. Honeymoon with GraphQL: • Reading docs • Exploring how to

    GraphQL • Comparing Apollo, Prisma, Relay, etc • Choosing IDE
  4. GraphQL Schema Definition Language: • A type has a name

    and can implement one or more interfaces • A field has a name and a type • The built-in scalar types are Int/Float/String/Boolean/ID • Enum is a scalar value that has a specified set of possible values
  5. I love to look in the mirrors Worse than D.R.Y.,

    DUPLICATION at: • Schema • DB tables • Endpoints Common solution: • Schema generation
  6. { __schema { types { __typename name } } }

    Introspection query Class reflections JavaScript have not Reflection
  7. import { Field, ID, ObjectType } from 'type-graphql'; import {

    Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; @Entity({ name: 'users' }) @ObjectType() export class User { @PrimaryGeneratedColumn('uuid') @Field(type => ID) id: string; @Column() @Index({ unique: true }) @Field() email: string; @Column() passwordHash: string; @Column({ nullable: true }) @Field({ nullable: true }) firstName?: string; @Column({ nullable: true }) @Field({ nullable: true }) lastName?: string; @Field() get name () { return `${this.firstName} ${this.lastName}`; } }
  8. import { Ctx, Query, Resolver } from 'type-graphql'; import {

    GraphQLContext } from '~/boundaries/graphql'; import { UsersRepository } from '~/repositories'; import { User } from '~/entities'; @Resolver(type => User) export class UserResolver { @Query(type => User) async currentUser ( @Ctx() context: GraphQLContext ): Promise<User> { return UsersRepository.find( { where: { id: context.user.id } }, ); } }
  9. I have Big Latency, perhaps Reasons: • Poor Performance •

    Too many DB queries • Client cache only Common solution: • Dataloader
  10. Precomputed data You store data for resolvers in NoSQL DB

    with structure similar GraphQL schema. Limitations: • You have data ownership
  11. We need data, but there are not decorators for such

    source type. For example: • Elastic Search • Data from external API • etc I miss something...
  12. // class class User { constructor(name, surname) { this.name =

    name; this.surname = surname; } } // class instance new User('Bart', 'Simpson'); // plain (literal) object ==============> const hero = { name: 'Gomer', surname: 'Simpson' }; POM
  13. import { plainToClass } from 'class-transformer'; fetch("users.json").then((users: Object[]) => {

    const realUsers = plainToClass(User, users); // now each user in realUsers is instance of User class }); const hero = { name: 'Gomer', surname: 'Simpson' }; hero instanceof User; // false
  14. Reasons: • SELECT all, because pagination and sorting implemented in

    the resolver • SELECT * FROM ... • JOIN is used I have Big Requests, Dear!
  15. GraphQL Resolver function signature: fieldName(obj, args, context, info) • obj:

    The object that contains the result returned from the resolver on the parent field • args: An object with the arguments passed into the field in the query • context: This is an object shared by all resolvers • info: AST, state of resolving. Example
  16. import { GraphQLResolveInfo } from 'graphql'; import graphqlFields from 'graphql-fields';

    import { Ctx, Info, Query, Resolver } from 'type-graphql'; import { GraphQLContext } from '~/boundaries/graphql'; import { UsersRepository } from '~/repositories'; import { User } from '~/entities'; @Resolver(type => User) export class UserResolver { @Query(type => User) async currentUser ( @Ctx() context: GraphQLContext, @Info() info: GraphQLResolveInfo ): Promise<User> { const fields = graphqlFields(info); return UsersRepository.find( { where: { id: context.user.id } }, { select: Object.keys(fields) } ); } }
  17. import graphqlFields from 'graphql-fields'; // ... async currentUser ( @Ctx()

    context: GraphQLContext, @Info() info: GraphQLResolveInfo ): Promise<User> { const fields = graphqlFields(info); return UsersRepository.find( { where: { id: context.user.id } }, { select: Object.keys(fields) } ); }
  18. • ACL • Query Complexity We have to talk about

    our relationship... import { Authorized } from 'type-graphql'; // ... @ObjectType() export class User { // ... @Authorized('Admin', 'CurrentUser') @Field() email: string; @Field(type => User,{ complexity: 20 }) friends: User[]; }
  19. Solutions: • For versioning graphql-doctor • For CI/CD eslint-plugin-graphql •

    For development WebStorm IDE plugin Let's not change anything?
  20. Ideas: • Have only one source of truth • Don’t

    repeat yourself • Use the right tools • Solve problem at the right level
  21. 47 THANKS! HAPPY CODING WITH GRAPHQL You can find me

    on Twitter as @galk_in Slides are available at speakerdeck.com/galkin or at my site galk.in