Log in
Seblog.nl

Seblog.nl

I don't understand why people prefer arrow-functions in Javascript in the following context:

const name = () => stuff();

vs

function name() {
  stuff();
}

... they are ideal for callbacks and map-operations, but when I want to declare a function, I want to see that keyword.

Dus, je lichaam is opgebouwd uit cellen, en de langst-levende cel leeft zeven jaar. Eigenlijk zijn alle cellen in je lichaam dus gemaakt van iets dat je de afgelopen zeven jaar hebt gegeten. Tenzij je jonger dan zeven bent, dan heeft je moeder meegegeten.

So, I've been addicted to Tetris for the last week or so. Last night I decided to do something else, and when I came across the Phoenix LiveView contest, I thought: why not try and make Tetris? #myelixirstatus

I was surprised by how far I got. I have watched videos of people coding games before, have been coding myself for years and years, but somehow games felt out of my league. But here, after a (long) evening, I got a game! I should've streamed it myself.

I mean I'm standing of shoulders of giants. I got so much for free from Erlang/Elixir, LiveView to hook it up, @chris_mccord's Snake example gave me the basic idea of using just <div>'s for blocks, the browser is doing the drawing and key-repeats for me.

But it felt magical, once the game got playable. I was hooked again to my own creation. And so many "features" from NES Tetris like sliding, tucking and spinning "just worked" in my first implementation (probably because I was close to their implementation).

It all started with this HTML based board, using flexbox to to the hard works of blocks for me. I later wrapped the board in a Game-struct, and added bindings for the scores.

How to get a game loop in Elixir? Just send yourself a message after, say, 500ms. The LiveView component has a handle_info/2, which queues the next tick and moves the current Tetromino down.

Thanks to the phx-keydown="keydown" in the template, we get messages in handle_event/3 for each key. Just delegating to my Game module. And yes, I use Vim, so I need those H, J and L. (Dropping with K is not implemented.)

My Game.move/2 got a bit complicated, some refactoring is in place. But I started with a Tetromino struct, with :x and :y keys and a :color. To move it down, you paint the current :x and :y :white, and then paint :y + 1 to the :color. Code below reduces for all four points:

To obtain all the points I got this wonderful Tetromino.points/1 function. It's ugly and was a pain to write out and get right, but it works like a charm.

Colors and rotations work similar, with Tetromino.rotate/1. (color_for_type/1 gets called in new/1, and it never changes once it is created.)

Then the messy Game.move/1: can you move down? Move down. Otherwise, call Board.clear_lines/1 to remove full lines and obtain the score, get a new random Tetromino and put it on the board. The game-over handling is a bit buggy still, I had only one evening.

Clearing the board is simple: reject rows of which all cells are not empty and use counts to add new rows to the top. The Board.color_at/2 gives the color for that particular cell. The guards against negative numbers prevent List.pop_at/2 to get items from the back of the list :)

I think that's mostly it! Again, I wish I had streamed it, it was very weird watching myself doing it, but also very rewarding. Keep coding, people, you can do this too!

Ok, ik heb dus net een soort zijbalk gemaakt die bovenaan de pagina blijft staan als je scrollt en ik ben daar onevenredig blij mee. Er is eigenlijk niets leuker dan een beetje CSS uitproberen op je eigen site.

Surf the web at lightning speed

I am at an airport, and I just saw a booth where I could access the web for free if I had the right card, or for a certain amount of euros per time unit. I saw the Microsoft Internet Explorer logo. All the computers where available, except for one. I assumed the girl that was using it worked there. Why else would you use a place like that?

It made me realise how much the web has changed. In another era, there were publishers, who put information on paper. These papers could then be brought by those who were interested in the information. It was a publishers world. The story has been told many times: the internet changed that, information became free, anyone could publish. But the web has evolved once more.

Why would I not want to use a computer in that booth even if it were free? Because I have no idea what to do on the web. Do I go to some news sites? Do I look up the time of my flight again? It would only be to kill time. Most of the public information is boring, the real fun is on Twitter, Facebook or maybe even my e-mail, where the information is tailored to my tastes. And no, I would not feel okay with entering my password into one of those things.

I should probably not call the social media part of the internet ‘the web’. It is partly not accessable without logging in, not all parts have clear URLs, it depends on a few giant websites. But there’s more fun on those silo’s, or at least: there is more of that promised content, published by anyone, everyone, especially your friends. And the best way of viewing it, is by authenticating myself as me. Luckily this is not a problem: I have a device in my pocket that is connected and authenticated 24/7. Compared to that, the web is a dull place, with unpersonal information.

Will the web make place for these giant silo’s? Is the time of publishing on your own site over? I hope not. I’m experimenting with this on this website, which is my personal website. There is a link in the upper-right corner which says ‘log in’. That is not a link for me, that is a link for you. You can log in using various methods (ok, only IndieAuth and Twitter are supported at the time of writing). After you are logged in you will see more posts, like my checkin into the airport. If I know you and shared a post especially with you, you will see those as well.

We still need some work on staying authenticated, preferably to one app that collects private posts for you, as you. But there is more web in this approach than we have in social media. And that is nice. I like more web. The web is exciting. Let’s not let it stay boring with only public, general information. Let’s share the personal here too. And let’s create a way to do that in a more private way, where you control who sees your posts. See you on the IndieWeb!

GraphQL Stitching versus Federation

Recently, I started working on a project that uses GraphQL Stitching, to bring multiple GraphQL Schema's from backend servers into one central Schema to consume for clients. Sadly, I cannot say I'm a fan of it. In researching how it all works, I found out that it was actually deprecated, and by digging a bit deeper, I found out that that happened last Thursday. I happily investigated the proposed alternative of Apollo Federation.

What's wrong with Stitching?

Let's first dive into what I don't like about Schema Stitching. I must admit that I'm no expert on the subject: I am still trying to grasp what's possible. And it seems like a lot is possible. One can grab fields from one service, enhance them with fields from other services, leave fields out, all kinds of things.

For example. If I have a backend service that does books, it could have a Schema like the following:

type Query {
  books: [Book]
  book(id: ID!): Book
}

type Book {
  id: ID!
  isbn: String!
  title: String
}

If you then have a backend service that does reviews of books, it could have a Schema like the following:

type Query {
  reviewsForIsbn(isbn: String!): [Review]
}

type Review {
  text: String
  stars: Integer
}

The thing you can do with Stitching, is that you can add a reviews field to the Book type, by using the isbn field on Book and using reviewsForIsbn(isbn:) to grab it. And this is already the part where I should leave out the details of how to do it: I have no idea, but I know it's possible, and even in weirder setups than this one.

The nasty thing about it is that it creates a dependency on both the isbn and reviewsForIsbn fields. This dependency is not visible from either backend service, only in the code of the stitching service. And since GraphQL is all about callbacks and resolver functions, that code in the stitching service can become difficult to read, especially if you do a lot of stitching.

I think the term stitching is excelent: if you keep stitching Schemas together, you will get a giant stitchwork full of yarn and everything will be connected to everything. And that's a bad thing, because it ties you down: you can't move a thing, if you pull one cord out, the whole thing might become undone.

With great power comes great responsibility. I think stitching is very powerful, but it's also very easy to shoot yourself in the foot with. As said: I was happily surprised to find out I wasn't alone and that it has been deprecated by Apollo. (Who, by the way, are the only GraphQL library that supported it.)

The new fancyness: Apollo Federation

How to bind multiple GraphQL Schemas together if you can't stitch them? Apollo's new answer is Apollo Federation, which is a pattern of defining Schemas, and a service called Apollo Gateway. Short summary: the Gateway reads the federated Schemas, and based on the information they provide, it stitches it all together, without you having to write any code in the stitching layer.

This information is given in the form of type anotations and some callbacks, that are described in a specification. For Javascript, you can just use the @apollo/federation package. For libraries (such as Absinthe for Elixir) you have some work to do.

But let's review the previous two services again, but now using Federation. Federation makes use of a few directives, which are designed to give more information to the query executor. They begin with an @ sign and can have parameters.

Consider the Books service again:

expand type Query {
  books: [Book]
  book(id: ID!): Book
}

type Book {
  id: ID!
  isbn: String!
  title: String
}

Not much changed here, other than the expand keyword in front of the Query type. The Books service, in this simple example, does not need any external data. The Reviews service, however, does:

expand type Query {
  reviewsForIsbn(isbn: String!): [Review]
}

type Review {
  text: String
  stars: Integer
}

expand type Book @key(fields: "isbn") {
  reviews: [Review] @external
}

There is some stitching here. The Reviews service knows about Books, but that is fine: a review would not makes sense without an object to review, in this case, books. Notice how, in the Schema, it is defined that, in order for the Reviews service to resolve the Book type, it needs an isbn. Seen from the existing Book type, the Review is then an @external type, which is provided by the Review service. Note that we could drop the reviewsForIsbn entirely now, but still maintain reviews on Book.

Some questions I think I have answers for but am not sure about:

  • Why add a Book type to the Reviews service? Because someone has to know, and I prefer it to be the service that has the other entity next to it's domain.
  • How does the Gateway know to call reviewsForIsbn? Well it doesn't: given the type and the requested @key(fields:), and some underwater endpoint, the Reviews service is now responsible to find reviews that match that type (Book) and key, and it can have a resolver for that.
  • Why can't the Books service just provide the full Book type? Because then knowledge about the link between Books and Reviews is in both services.

I don't pretend to understand all the implications of both approaches, but intu¬tively, this second approach seems better to me. There is no code that we have to write in the Gateway anymore, and the Schema is split up between the services, that only know their own part about the shared entities.

Downsides: gotta bring it to Absinthe

Unfortunately, like with Stitching, Apollo for Javascript is the only library that supports this thing at the moment. As mentioned, there is a spec describing what to do to support this elsewhere (still using the Apollo Gateway), but it is very early days.

I tried some stuff with Absinthe, and was happy to see macros for directives. After some exploration, however, I came to the conclusion that the directives you can define with these, are only useable in the Schema you put out, and thus in your queries. Since the Schema itself is defined with macros in a DSL, I cannot use the directives there. I will have to set up my own macros for it, and that is it's own rabbit hole.

Moreover: in the current release of Absinthe, there is not yet a way to get your parsed Schema out. This Schema, however, has to be send to the Gateway, for it to work. I tried working around it by defining the Schema twice (by hand and with the DSL), but there are still a lot of rabbit holes in the union _Entity (which consists of all types that use the @key directive) and scalar _Any (which maps to all @external types).

With Stitching, all the work is done in the external service, so your backend services do not have to know about it. With Federation, all your backend services have to be aware that they are part of something bigger in order to participate in that bigger picture.

Still, I really like the idea, and I think it will hold better than stitching all the things manually.

Meer laden