The GoActivityPub library contains the building blocks for adding ActivityPub functionality to both client and server applications for the Go programming language. It is a robust, “batteries included” product with a high level of interoperability with the rest of the Go ecosystem.
The focus is on high spec compliance, minimal external dependencies, and minimal computing requirements.
The library is divided into functionally independent modules connected through interfaces.
The official repositories for the project are hosted under the go-ap GitHub organization.
The high level view on the project offers the following distinct pieces:
- Activity Vocabulary data types for Go.
- The ActivityPub state machine which deals with processing different activities, handling their side effects and disseminating them and their objects to recipients and collections.
- Custom storage mechanisms specifically created for ActivityPub objects and supporting a standardised API.
- A custom HTTP client with an extended API for interacting with ActivityPub servers for both fetching data and submitting activities.
- Authorization mechanisms for this client that support both server to server and client to server algorithms.
For more information go to the description page.
Design decisions
The main problem of implementing the ActivityPub specification in the Go programming language stems from the very dynamic nature of JSON-LD, which is used as its data format, and the limited (in this respect) features of the Go type system.
This apparent incompatibility stems from the fact that an object property in the ActivityPub specification can have any of the following values:
- an IRI which can be dereferenced to an ActivityPub object
- a full ActivityPub object
- an array of ActivityPub objects
- an array of IRIs to ActivityPub objects.
Because the Go type system can’t express this union explicitly, we rely on
implementing these four meta-types independently and giving them unifying behavoiur
through the interface: ObjectOrLink.
Basically if any type implements this interface[1], it can be used with the
GoActivityPub modules.
To keep in line with Go’s interface guidelines, we have tried to keep the methods this interface exposed to a minimum, and this led to a very limited guaranteed API for the module.
To circumvent the fact that most of the library deals with instances of this interface, we had to create the convenience functions that allow a developer to assert them to actual useful ActivityPub structs.
They are the functions starting with OnXXX and ToXXX in the module and give
the possibility of treating any struct that impelments ObjectOrIRI as an XXX
ActivityPub object, where XXX can be an Activity, an Actor, Object,
Link samd.
This is incidentally the mechanism through which we allow extending the default
AP vocabulary by other modules. Other developers can create their own type
YYY, implement the interface and add their OnYYY functionality[2].
The first caveat about this type of logic is that it relies heavily (at least
currently) on the fact that the memory layout for each type needs to be
identical for the properties which are common. The properties need to have the
same order and the same type. Basically the existing ToXXX functions
work as a cast does in plain C. It takes the pointer the interface holds, and
converts it to the desired type (using the unsafe package) relying on the fact
that the common properties of the Objects are at the same offsets from the
pointer[3].
[1] The interface matches the ActivityStreams separation between Object compatible structs and Link compatible structs.
[2] This also requires overriding the default typer function, which is used to
return the correct type based on the YYY.Type property.
[3] This behaviour is risky and it can probably change without warning if the Go dev team changes the language’s memory model at a later date. I hope I’ll find a cleaner way to implemnt this in the future, but for now it serves its purpose.
Pitfalls
I compiled a list of pitfalls when using the modules.