O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

PHP: GraphQL consistency through code generation

156 visualizações

Publicada em

Slides for the talk given at the Berlin PHP user group meetup

How to guarantee consistency of PHP GraphQL implementation to the schema definition with the help of code generation.

Publicada em: Tecnologia
  • Entre para ver os comentários

  • Seja a primeira pessoa a gostar disto

PHP: GraphQL consistency through code generation

  1. 1. GraphQL Consistency through code generation by Aleksandr Obukhov
  2. 2. Aleksandr Obukhov Lead Backend Engineer at AMBOSS GmbH https://github.com/obukhov @dclg_en
  3. 3. GraphQL Consistency through code generation by Aleksandr Obukhov
  4. 4. What is GraphQL?
  5. 5. Rise-your-hands game Who is well versed with the GraphQL specification? Who tried to implement GraphQL server with PHP? Who is supporting GraphQL server in a production environment?
  6. 6. GraphQL Server implementations are available for multiple languages, including Haskell, JavaScript, Python, Ruby, Java, C#, Scala, Go, Elixir, Erlang, PHP, and Clojure - API specification and runtime - developed by Facebook - published in 2015
  7. 7. RPC API Interaction is just a remote procedure call: - It has “function name” - It has arguments - It has return value (response) REST All data is a resource: - It can be created / read / updated / deleted - Resource is identified by URL - It can be connected to another resources through relation Concepts of API
  8. 8. RPC REST - Versioning of resource schema - Limited set of operations with resources Limitations - Versioning of endpoints and response data - Pre-designed structure of input and output
  9. 9. Why do we like GraphQL? Validation of input / output data Tools: automatic documentation based on schema Ready-to-be-used specification Schema defines: data types, abstractions, relations Operations defined by root Query/Mutation objects All this can be found in other specifications
  10. 10. Why do we like GraphQL? Query is disambiguous: - no wildcard fields – you don’t need resource versions, - type assertions, interfaces and unions – easy to extend with new types Nice: - built-in deprecation mechanism Instead of implementing complete endpoints, developer defines the way to resolve relations – easier to reuse code. Schema definition language is simple and robust
  11. 11. Transport layer How do we GraphQL? webonyx/graphql-php Request Query validation Mapping to resolvers Assembling result Response validation & formatting Response Your app API layer ?
  12. 12. Global field resolver to resolve object properties class DefaultFieldResolver { // ... public function __invoke($source, $args, $context, ResolveInfo $info) { $fieldName = $info->fieldName; $property = null; if ($source instanceof TypeInterface) { $method = 'get' . ucfirst($fieldName); if (!method_exists($source, $method)) { throw new FieldNotImplemented('Field <' . $fieldName . '> is not implemented for type <' . $info->parentType . '>'); } $property = call_user_func_array([$source, $method], $args); } $fieldValue = $property instanceof Closure ? $property($source, $args, $context, $info) : $property; return $fieldValue; } }
  13. 13. Adapters to represent API objectTypes class TaxonomyType extends AbstractTaxonomyType { /** @var TaxonomyDTO */ private $taxonomy; // ... /** @return int */ public function getTreeDepth() { return $this->taxonomy->getTreeDepth(); } /** @return TaxonType[] */ public function getTaxa() { return $this->dataLoaderRegistry->get('taxonByParentTaxon')->load($this->taxonomy->getRootTaxonId()); } }
  14. 14. What are challenges there Return values is up to your resolver implementation, but still duplicates schema Resolvers receive validated data, but it is still presented as associative array All inconsistencies can be spotted in the runtime only Enum values needs to be duplicated Input and output types field names needs to be duplicated You define a set of resolver functions that implement application BL
  15. 15. GraphQL schema: code generation Tom Landvoigt & Alex Obukhov
  16. 16. What schema defines Input Object Type Query Type Mutation Type EnumInterface Object Type Unions
  17. 17. What schema defines Input Object Type input FeedbackInput { message: String! type: FeedbackType! source: FeedbackSource! } Query Type type Query { user(eid: ID!): User } Mutation Type type Mutation { submitFeedback(feedback: FeedbackInput!): Boolean } Enum enum Stage { preclinic clinic doctor } Interface interface Entity { eid: ID! } Object Type type User implements Entity { eid: ID! stage: Stage! firstName: String lastName: String }
  18. 18. Code generation: Object Type type User implements Entity { eid: ID! stage: Stage! firstName: String lastName: String } abstract class AbstractUserType implements EntityInterface { /** @return string */ abstract public function getEid(); /** @return string */ abstract public function getStage(); /** @return null|string */ abstract public function getFirstName(); /** @return null|string */ abstract public function getLastName(); } Abstract class can be extended by multiple different classes
  19. 19. Code generation: Object Type class UserType extends AbstractUserType { private $user; /** @return string */ public function getEid() { return $this->user->getUuid(); } ... } class AdminType extends AbstractUserType { private $admin; /** @return string */ public function getEid() { return $this->admin>getExternalId(); } ... }
  20. 20. Code generation: Interface interface Entity { eid: ID! } interface EntityInterface { /** * @return string | int */ public function getEid(); } Abstract class implements this interface automatically
  21. 21. Code generation: Input Object Type input FeedbackInput { message: String! type: FeedbackType! source: FeedbackSource! } class FeedbackInputType { /** @var string */ private $message; /** @var string */ private $type; /** @var FeedbackSourceType */ private $source; public function __construct(array $inputValues) { $this->message = $inputValues['message']; $this->type = $inputValues['type']; $this->source = new FeedbackSourceType( $inputValues['source'] ); } Input is wrapped to value object recursively
  22. 22. Code generation: Enum enum Stage { preclinic clinic doctor } class StageEnum extends Enum { const PRECLINIC = 'preclinic'; const CLINIC = 'clinic'; const DOCTOR = 'doctor'; /** @inheritdoc */ public function getValues() { return [ self::PRECLINIC, self::CLINIC, self::DOCTOR, ]; } } Constants can be used to guarantee the consistency of schema via static analysis
  23. 23. Code generation: Union union NodeContent = File | Folder type Node { eid: ID! firstName: String content: NodeContent } class NodeType extends AbstractNodeType { /** @return FileType|FolderType */ public function getContents() { ... } } Union definitions are used to declare @return in the docblcok
  24. 24. Benefits - Easier to kick-off new type – just extend and use IDE to create stubs - Ready-to-be-used value objects for Enum and Input - Automatic interfaces implementation - Docblock support for types defined by schema - Schema inconsistencies can be detected with static code analysis
  25. 25. Thank you! Questions?

×