tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#enjin-graphql-api')" >Enjin GraphQL API
Enjin's GraphQL API is critical to their mission of putting blockchain tech in the hands of game developers, allowing them to build both client and server-side integrations into their games.
When I started working on Enjin Cloud there was already a v1 API in place, but it had a few problems:
Authentication, access token handling and app selection needed a rethink. API consumers could authenticate with either app or user tokens, and end up in a variety of states, with inconsistent access restrictions.
Ethereum Parity nodes aren't the fastest things ever, and some queries needed several hits to resolve their fields, so even small list queries were noticeably slow.
GraphQL queries often returned long unpaginated results, and nothing logged what clients were actually requesting.
Many queries and mutations violated the single responsibility principle, so the user's intent was often unclear, which caused problems for both access control and performance.
I spent most of my time simplifying resolver logic, stripping features from the v1 API, and adding test coverage, so we knew what was possible and whether it worked. I split the API into schemas per audience (wallet, platform, Beam and others) and added pagination. I renamed and deprecated fields carefully, leaving 43 deprecation notices rather than broken integrations. I also built a custom GraphQL Playground with a tab per schema and your apps' credentials filled in.
tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#tracking-api-usage-with-kibana')" >Tracking API usage with Kibana
As I looked into Enjin's GraphQL API it became apparent there were many arguments, fields and queries we'd want to deprecate. The GraphQL spec supports deprecating fields, but not arguments, and any change risks breaking integrations, so we needed to see what clients were actually using in production.
Traditional API logging tools don't help much with GraphQL, so I added resolver middleware support to GraphQL Laravel. Every resolver then ran through a pipeline of authentication, platform locks, pagination, validation and logging, and each query's fields and arguments were sent to Logstash, to visualize in Kibana. I contributed a handful of other fixes to GraphQL Laravel, graphql-php and Laravel along the way.
After I shipped this we were able to find a number of problems behind slow queries, and make data-driven decisions about what we removed.
tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#giving-back-to-graphql-in-php')" >Giving back to GraphQL in PHP
Most of what we needed from GraphQL in Laravel didn't exist yet, so where it made sense I built it upstream rather than around it. Seven of those pull requests were merged:
Resolver middleware in rebing/graphql-laravel, so apps can log what's queried and authorize before validating
Type modifiers in
GraphQL::type(), like[String!]!, and the built-in scalarsA macroable GraphQL facade, a fix for per-schema types, and a tidier README
Field selection on scalar fields in webonyx/graphql-php, which used to crash
Arr::hasAnyin Laravel itself, and a fix to the array cache'sforget()
tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#testing-graphql-with-phpunit')" >Testing GraphQL with PHPUnit
The team had struggled to test the API, mostly because GraphQL is non-trivial to exercise with Laravel's test client out of the box, and because single responsibility problems in the core classes that talked to the blockchain made mocking difficult. I made it my mission to make it easy for developers on the team to write tests for their changes.
The steps I went through to get there were:
Moving Ethereum hexing out of traits into dedicated support classes, so that classes could be mocked without breaking string manipulation.
Separating networking logic into client classes, so that we could easily mock them with simple fakes or Guzzler.
Extending the base TestCase and TestResponse classes with helpers that make it easy to perform GraphQL queries and mutations, and assert on the responses.
Simplifying the authentication strategies, and breaking the single schema up into several, so that resolvers had fewer access control responsibilities.
Stripping unrelated functionality, and fanning overcomplicated mutations out into several that better communicated the intent of the user.
The suite grew to nearly 900 tests, around 500 of them mine, and made it a lot easier to establish what was possible with the existing APIs, and to design what the new schema should look like in v2.
tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#next-steps-with-the-v2-api')" >Next steps with the v2 API
Enjin's API is unusual in that it has two primary consumers: game developers, through their server backends, and players, through a client-side game. Many games want to do everything server-side, and so need a broad set of access controls, but when they generate an access token for a player and pass it down to the client, those players need very limited abilities.
Many challenges in the v1 API came from queries and mutations trying to serve a broad range of user types at once, which created lots of noise and hid what each resolver's core business logic really was.
For the v2 API I proposed splitting it into two distinct schemas, app and player, to remove this ambiguity. The schema would make it clear which queries were available to whom and how to authenticate for them, while letting us drop lots of deprecated behavior. I prototyped it before I moved on.
tapped = false, 1500); navigator.clipboard?.writeText(location.href.split('#')[0] + '#migrating-from-vue-material-to-vuetify')" >Migrating from Vue Material to Vuetify
The frontend was originally written in Vue Material, but that project seemed somewhat stagnant, and we needed to make fairly significant changes to our implementation anyway, so I decided it was time to move to Vuetify, to use its more established components, such as data table pagination. Tables, forms, sign-in and the toolbar came first.
Some of the other significant frontend work I led included:
Switching to a private API, so that we could use session auth, and keep the frontend's concerns apart from the public API's backward compatibility.
Moving from access token to session auth, to standardize the way the app worked with the rest of the Laravel suite, and remove some bespoke behaviors from the auth system.
Standardizing interfaces under "page components" that wrapped up common theming, loading spinners, calls to action, and general layout.
Significant improvements to the mobile UI, embracing material design, and making sure data tables collapsed in a way that was both attractive and useful.
Laravel Nova for admins, and the upgrade to Laravel 6.
The result was a significant improvement for both desktop and mobile users, and made it a lot easier for other developers to add pages that conformed to the application's style guide.
