Enjin build a suite of user-first blockchain products that enable game developers to easily create, manage, distribute, and integrate blockchain assets. I worked on their Laravel + Vue SPA and GraphQL APIs.
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.
At the point I started working on Enjin Cloud there was already a v1 API in place, but there were a few problems:
Authentication, access token handling and app selection needed a rethink. API consumers could authenticate either via 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 various queries could require multiple hits to resolve their return fields, meaning there was notable slow responses even on small list queries.
GraphQL queries were often yielding long unpaginated results, and there was no tracking in place to log what end users were actually requesting.
Many queries and mutations violated single responsibility principle in their design, meaning the intent of the user was often unclear, which lead to issues with both access control and performance.
I spent a majority of my time simplifying resolver logic and stripping features from the v1 API, and adding test coverage so we knew what was possible and whether it worked.
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. Field deprecation is supported by the GraphQL spec, but argument deprecations aren't, and any changes typically risk breaking integrations; to do them we needed insight into what clients were actually using in production.
Traditional API logging tools don't help much with GraphQL, so I added resolver middleware support into GraphQL Laravel and set up an ELK stack so we could intercept the processed AST and fire some logs out to a Logstash for visualisation in Kibana.
After I shipped this we were able to identify a number of problems that were leading to slow queries and make data-driven decisions regarding what we removed.
Testing GraphQL with PHPUnit
I wrote over 800 PHPUnit tests for Enjin Cloud, most of which added coverage to the GraphQL API. The team had struggled in the past to exercise the API mostly because GraphQL is non-trivial to test out of the box with the Laravel test client, and because of SRP problems in core classes that interacted with the blockchain making mocking difficult. I made it my mission to make it easy for developers on the team to be able 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 out networking logic into client classes so that we could easily mock these aspects 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 the responses.
Simplifying the authentication strategies used and breaking up the single schema into multiple so that resolvers had fewer ACL responsibilities.
Stripping unrelated functionality and fanning out overcomplicated mutations into multiple mutations that better communicated the intent of the user.
The resulting test suite made it a lot easier to establish what was possible via the existing APIs, and design what the new schema should look like in v2.
Next steps with the v2 API
Enjin's API is somewhat unique in that there are conceptually 2 primary consumers: games developers, via their server-backend, and players, via a client-side game. Many games may wish to do everything server-side, and in which case they're going to want to be able to have quite a broad set of access controls, but when they generate an access token for a player and pass it down to the client-side, they're going to need very limited abilities granted to those players.
Many challenges in the v1 API stemmed from queries and mutations trying to serve a broad range of user types all at once, which lead to lots of noise making it unclear what the core business logic fundamentals of these individual resolvers really was.
In the v2 API I proposed that we break out this API into 2 distinct schema: app and player, that removed this ambiguity, and made it clear though the schema which queries were available to whom, and how to authenticate for them, while also allowing us to drop lots of deprecated behaviours from the new schema.
Migrating from Vue Material to Vuetify
The frontend was originally written in Vue Material but given the project seemed somewhat stagnant and we needed to make some fairly significant changes to our implementation anyway I decided it was also time to move the project over to Vuetify so that we could leverage the more established components such as data table pagination.
In addition to this, some of the other significant frontend works I lead included:
Switching to consuming a private API so that we could use session auth, and separate the concerns of the frontend away from maintaining the backwards compatibility of the public API
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 behaviours from the auth system.
Standardizing interfaces under "page components" that wrapped up common theming, loading spinners, call to actions, 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.
The resulting experience was a significant improvement for both desktop and mobile users, and made it a lot easier for other developers to add pages into the UI that conformed to the application styleguide.
Steve is an experienced, high-performing developer. He was able to laser-focus on certain architectural issues in our v1 codebase and was not afraid of the difficult work needed to do a refactor. We brought him onto our team to improve our complex blockchain API codebase that depended on a lot of different separate applications. He was quickly able to understand the entire project's architecture and improve what we had. Steve was diligent with code reviews & pull requests and constantly encouraged us to improve our own internal processes. His commits were frequent and he was able to resolve a ton of bugs and lay the groundwork for our v2 backend. He definitely doesn't mince words and will tell you outright if there's a better way to do things. I would highly recommend working with Steve!