-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Graphs
Graph data structures have the advantage of supporting all other data types, such as key/value pairs, relational or table-based data, documents, and even hypergraphs. They look like this:
All of these records can be updated at extremely fast speeds in a p2p network, thanks to the conflict resolution algorithm, to support things like realtime GPS coordinates or live streaming video. To see a bunch of visual examples of the variety of applications graphs can be used for, check out the main repo. The reason that this is the case is because in mathematics any data structure can be represented as a graph. For instance tables as "matrices" or documents as "trees", but not all graphs can be represented accurately as just a tree alone.
It is also easy to prove this out with GUN in code by creating interconnected data using only a couple core API methods. So here is an introductory guide on how to combine a variety of different data types together.
Note: This article requires gun v0.8.x or above.
First let's instantiate the database.
var gun = Gun();
Then we'll add some people to our database using a simple key/value approach.
var alice = gun.get('person/alice').put({name: 'alice', age: 22});
var bob = gun.get('person/bob').put({name: 'bob', age: 24});
var carl = gun.get('person/carl').put({name: 'carl', age: 16});
var dave = gun.get('person/dave').put({name: 'dave', age: 42});
Note: If no data is found on the key ('person/alice', etc.) when we
.get
it, gun will implicitly create and update it upon a.put
. This is useful and convenient, but can be problematic for some types of apps. If you want to check if the data does not exist, use.not
first.
What if we want to get their data? We can either chain off of the reference directly or get it again:
alice.on(function(node){
console.log('Subscribed to Alice!', node);
});
// Note: used to be `.val` but this has changed to `.once`
gun.get('person/bob').once(function(node){
console.log('Bob!', node);
});
Note: GUN is a functional reactive database for streaming event driven data, gotta hate buzzwords - right? This means that
.on
subscribes to realtime updates, and may get called many times. Meanwhile.val
.once
grabs the data once, which is useful for procedural operations.
Now lets add all the people into a set, you can think of this as a table in relational databases or a collection in NoSQL databases.
var people = gun.get('people');
people.set(alice);
people.set(bob);
people.set(carl);
people.set(dave);
Note:
.get
and.put
are the core API of gun, everything else is just a convenient utility that wraps around them for specific uses - like.set
for inserting records.
It is now easy to iterate through our list of people.
// Note: `.val` changed to `.once`
people.map().once(function(person){
console.log("The person is", person);
});
Note: If
.map
is given no callback, it simply iterates over each item in the list "as is" - thus acting like a for each loop in javascript. Also, everything is continuously evaluating in GUN, including.map
, so it will get called when new items are added as well as when an item is updated. It does not iterate through the whole list again every time, just the changes. This is great for realtime applications.
Next we want to add a startup that some of these people work at. Document oriented data does a perfect job at capturing the hierarchy of a company. Let's do that with the following:
var company = gun.get('startup/hype').put({
name: "hype",
profitable: false,
address: {
street: "123 Hipster Lane",
city: "San Francisco",
state: "CA",
country: "USA"
}
});
Now let's read it out!
// `.val` changed to `.once`
company.once(function(startup){
console.log("The startup:", startup);
});
Note: The data given in the callback is only 1 layer deep to keep things fast. What you'll see logged out on
startup.address
is not the address itself, but a pointer to the address. Because documents can be of any depth, GUN only streams out what you need by default, thus optimizing bandwidth.
So what if you want to actually access the city property on the company's address then? .get
also lets you traverse into the key/value pairs on sub-objects. Take this for example:
// Note: `.val` changed to `.once`
company.get('address').get('city').once(function(value, key){
console.log("What is the city?", value);
});
Good news! We just found out the company got funding and moved to a new office! Let's go ahead and update it.
gun.get('startup/hype').put({ // or you could do `company.put({` instead.
funded: true,
address: {
street: "999 Expensive Boulevard"
}
});
Note: GUN saves everything as a partial update, so you do not have to re-save the entire object every time (in fact, this should be avoided)! It automatically merges the updates for you by doing conflict resolution on the data. This lets us update only the pieces we want without worrying about overwriting the whole document.
However documents in isolation are not very useful. Let's connect things and turn everything into a graph!
var employees = company.get('employees');
employees.set(dave);
employees.set(alice);
employees.set(bob);
alice.get('spouse').put(bob);
bob.get('spouse').put(alice);
alice.get('spouse').get('employer').put(company);
alice.get('employer').put(company);
dave.get('kids').set(carl);
carl.get('dad').put(dave);
carl.get('friends').set(alice);
carl.get('friends').set(bob);
Note: We can have 1-1, 1-N, N-N relationships. By default every relationship is a "directed" graph (it only goes in one direction), so if you want bi-directional relationships you must explicitly save the data as being so (like with Dave and his kid, Carl). If you want to have meta information about the relationship, simply create an "edge" node that both properties point to instead. Many graph databases do this by default, but because not all data structures require it, gun leaves it to you to specify.
Finally, let's read some data out. Starting with getting a key/value, then navigating into a document, then mapping over a table, then traversing into one of the columns and printing out all the values!
// Note: `.val` changed to `.once`
gun.get('person/alice').get('spouse').get('employer').get('employees').map().get('name').once(function(data, key){
console.log("The employee's", key, data);
});
Awesome, now run it all together: https://jsbin.com/sebanirini/edit?js,console (Hit "Run", all logs except for the last one have been commented out).
GUN is that easy! And it all syncs in realtime across devices! Imagine what you can build?
-
Or IoT apps with timeseries analysis and live temperature data visualizations.
-
How about multiplayer VR experiences, or React apps like online games? Or realtime GPS tracking for autonomous drones that deliver burritos or Uber/Lyft like apps.
-
Maybe you just want to create a social networking app, getting started with a basic server and using Angular or Webcomponents/Polymer instead. Better learn about P2P logins, security, and authentication with our 1 minute video explainers crash course on encryption!
Whatever it is (except for banking), we hope you are excited and tackle it with gun! Make sure you join the chat room (everybody is nice and helpful there) and ask questions or complain about bugs/problems. Or if you think your company might be interested in using gun, we have some great partnership plans (as well as some donation options, if you want to personally contribute some yummy meals to our tummy)!