Soukai: A different way to query PODs


#1

I’ve been working for a while on an ODM called Soukai.

The idea of using this library is that it has a common interface to define data models, and using different engines it can interact with different data sources. This is similar to existing Active Record implementations. One of such engine is soukai-solid, which allows to store models in Solid PODs.

I know what some think about Active Record, and the eternal discussion if it’s correct using it vs Data Mappers. But I won’t go into that, suffice to say that this is an opinionated library and you don’t have to use it if you don’t like it. I just want to share my work so that it can help others :).

On that note, I think LDflex is already a great tool to work with Solid using the Data Mapper paradigm. Although I can’t say for sure because I haven’t used it extensively, my apologies if it isn’t a fair comparison.

Example

So, how does this work? Here’s the classical example of interacting with a foaf:Person document.

First you’d define the Person model:

import { FieldType } from 'soukai';
import { SolidModel } from 'soukai-solid';

class Person extends SolidModel {
    static rdfContexts = {
        'foaf': 'http://cmlns.com/foaf/0.1/',
    };

    static rdfsClasses = ['foaf:Person'];

    static fields = {
        name: FieldType.String,
        friendUrls: {
            type: FieldType.Array,
            rdfProperty: 'foaf:knows',
            items: { type: FieldType.Key },
        },
    };
}

And after setting up the engine, you can use it as such:

const alice = await Person.find('https://example.com/alice/card#me');

alert(`Hello, ${alice.name}!`);

// You should see something such as {"name": "Alice", "friendUrls": [...]} in the console
console.log(alice.getAttributes());

// Add a new friend
await alice.update({
    friendUrls: [
        ...alice.friendUrls,
        'https://example.com/john/card#me',
    ],
});

This is of course a very simple example, if you are interested in learning more I suggest that you read the documentation of both soukai and soukai-solid.

Current Status

I’ve used the library on a task manager I’m developing for Solid, Solid Focus. You can look at the source code of that project to learn how this is used in a real-world application.

This is still one of the very first versions of the library, but I’ve tried to write a thorough documentation of the current features. I’m eager to hear your opinions and get some feedback.


#2

Dear NoelDeMartin I tried your application. I logged in with my inrupt solid account. I created a demo task list. When I relogin, all my data has gone. So doesn’t the app store the data to my pod. Does it only gets my name from the pod?


#3

Nice! Just wondering, and I don’t know if this is possible or difficult, but have you considered generating soukai models from shapes given in shex or shacl?


#4

You should check your POD to see if the data was created or not. I know there is a problem on the current deployed versions of inrupt and solid community PODs, and I reported it a some time ago: https://github.com/solid/node-solid-server/issues/1120 Seems like it was fixed for a while but there’s been a regression and it’s happening again. For me it works on node-solid-server 5.0.0 (commit hash bf5541ca35a50bc59f566230f710e27cd915beb0).


#5

Yes! I actually don’t know much about shapes other than the basics, but I’ve considered having an rdfShape property or something of the sort. Maybe if I become familiar with shapes I’ll do it :).


#6

Will data created with each soukai-solid model resulted in a single turtle file?

How do you handle cross file query?


#7

I guess you probably weren’t asking me for an explanation of shapes, since it’s pretty well explained in the shape expressions primer, and all I’ve done is read the primer and that was months ago. But I think I get the main idea. You said you know the basics, so you probably already know this, but in case it’s helpful I’ll say it here and then maybe any experts out there can correct me if I’m wrong:
An ontology describes an open world, whereas a shape describes a closed one. So for example a car ontology doesn’t say a car couldn’t have emotions or children, but if a car shape doesn’t include anything about emotions or children, then a car can’t have them.


#8

How does LDflex do that?


#9

Yes, each model will be a single file on the server. Cross-file queries right now are only supported using relationships, so that’s probably something to improve for some use-cases.

What this library does at its core is treating models as LDP Resources, so saving a new model instance will for example add the ldp:Resource class automatically, and there are some properties like ldpContainer to work with LDP Containers. One relationship that containers and resources have is ldp:contains, so that’s how you can get multiple models at once.

The interaction with a Solid POD is actually within a single file, so you can look at that if you want to know exactly what’s happening on the network: https://github.com/NoelDeMartin/soukai-solid/blob/master/src/solid/SolidClient.ts


#10

Yes I have already read the primer and this post as well: Shaping Linked Data apps. But I haven’t really used them, so I only know them from theory. If I wanted to add them to the library I wouldn’t be confortable without having used them in practice first.

But yes, the concept seems to fit quite well with how I’ve done this so far. What I wrote on this post were only some basic examples, but you can also define other things on the model like this:

class User extends SolidModel {
    static rdfContexts = {
        'foaf': 'http://cmlns.com/foaf/0.1/',
    };

    static rdfsClasses = ['foaf:Person'];

    static fields = {
        name: {
            type: FieldType.String,
            rdfProperty: 'foaf:name',
            required: true,
        },
        inbox: {
            type: FieldType.Key,
            rdfProperty: 'foaf:mbox',
            required: true,
        },
    };
}

#11

Hi @NoelDeMartin,

Good stuff, we definitely need experimentation regarding different programming interfaces to remote data. A query abstraction to Linked Data is, in my opinion, the way forward.

I would suggest to indeed contrast Soukai to LDflex, so people clearly see the similarities and differences.
The example you gave above would be the following in LDflex:

const alice = solid.data['https://example.com/alice/card#me'];
alert(`Hello, ${await alice.name}!`);

Or, alternatively:

const alice = 'https://example.com/alice/card#me';
alert(`Hello, ${await solid.data[alice].name}!`);

Live example

As you can see, the main difference in this situation is the position of the await. Soukai would prepopulate the entire object, and from that moment on treat it as something in-memory. LDflex always treats things as remote objects (but that interface is independent of when exactly the fetching happens).

The other examples translate to:

const alice = solid.data['https://example.com/alice/card#me'];
const attributes = await alice.properties;

Live

and

const alice = solid.data['https://example.com/alice/card#me'];
const john = solid.data['https://example.com/john/card#me'];
await alice.friends.add(john);