Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

ℹ️ This article is based on the master branch of the project still in development.

ent is an ORM created by the team Facebook Connectivity. Motivated by the lack of tools able to query data as a graph in the Go community, as well as a lack of 100% type-safety ORM, ent has been designed to address those issues.

Graph concept

ent represents the concept of graphs by a list of entities and edges without any limit in the representation. Let’s use the example available in the repository:

data represented with graph

In this example, there are three entities, Group , User , Pet , linked all together that can be traversed multiple ways. This diagram can be described by the tool entc provided with the project thanks to the command entc describe :

The relations between the entities are described by edges that are translated internally to fields, tables, and foreign keys. Here is the generated database diagram in MySQL:

Contrary to the traditional ORMs where each relation is explicitly represented in the entity, ent exposes only the attributes of the entity and a method to go through the edges of the entities.

Queries

The project provides excellent documentation full of examples of the different possible queries and usage of the ORM. Let’s review one of them with a query that involves all entities:

From the friends of the users that are in the admin group, we want the owners of all the pets friends with their pets.

Here is a representation of the query:

query in the graph

The query suggested in the example is self-describing:

query on the graph

As seen in the previous section, each entity exposes methods to query through each of their edges: Group exposes the admin edge via QueryAdmin() , that itself exposes its friends edge via QueryFriends() , and so on. The code generation that is the core of the tool manages to create all those methods.

Code generation

The code generation, available from the command entc , is an essential part of the project and makes it even more powerful. The template engine is the base of the generation and allows developers to modify the queries and exposed methods to their needs, making the ORM flexible enough for any customization.

The default templates for the entities will provide all the files and methods to create, update, delete, and query each entity and their edges in a traversable way. However, everything is a template in the code generation. You can customize the way it communicates with the database, the migrations, and so on.

The project also provides an example of a template that implements the Node interface for GraphQL. The generated code makes all our entities compatible with this interface:

This template system allows you to generate the code that suits your project perfectly.

Performance

It is now interesting to go under the hood and see how the query is built. Here is, for MySQL, the generated query of the previous example:

generated query on the graph

The ORM uses nested queries that make it traversable without any limit. However, it is also doable with join queries. Here is the equivalent version of the nested query with joins:

join query on the graph

Although the nested query can be more readable, since a hierarchy of select statements composes it, most of the time, the join queries will run faster. However, in this case, since the ORM traverses the data through the edges that are related, thus indexes, the plan created by the server is the same:

plan for the subqueries

plan for the join

The ORM also supports more complex queries to find nodes through the graph, such as:

We want to get all pets which have friends with an owner that is friend with the admin of the group.

This query is rather difficult to write in SQL. It’s almost impossible with a regular ORM, but quite simple with the ent :

Also, since ent does not have to deal with the aggregation of multiple nodes from multiples domain like the traditional ORMs, the performance should always be great. A quick benchmark on the first query will show the extra work added by the package:

name time/op

Traverse-8 179µs ± 5%



name alloc/op

Traverse-8 21.4kB ± 0%



name allocs/op

Traverse-8 414 ± 0%

Now, let’s run it again without the actual query to the database:

name time/op

Traverse-8 64.4µs ± 1%

The query itself took most of the time of the previous benchmark to the database. The latency introduced by the project is low and promises an excellent future for this project. For more details about the development, the project has released a roadmap on Github.