Access points
Introduction
One of the main goals of a Linked Data Platform is to link data (obviously). Such links can exist within a domain or between different domains.
With Carbon LDP you can link data in two different ways:
- Parent-child relation (hard link)
Denotes a direct parent-child relation in which a document can have child documents. This means that whenever the parent is deleted, the children and the children of the children (and so on…) will also be deleted. - Membership relation (soft link)
A soft link can be made if you want to list a document, let’s say document B as a member of another document, such as document A. By creating this type of link, if you delete the document linking the members (document A) only that document is deleted; any documents that were a member of it (e.g. document B) are left untouched.
Membership relation
Notably, the core of a soft link is listing documents as members of another one. Then, how is a membership relation made with Carbon?
All Carbon documents can maintain a list of members. The relation between the document and its members can be configured through the properties:
hasMemberRelation
: Name of the property that will hold the array of members (by defaultmembers
)isMemberOfRelation
: Name of the property that each member document will have, that links back to the document they are members of (none by default)
These properties are configured in the document that will maintain the member array. Members can be added, removed, listed or retrieved through the following methods in the documents
endpoint (and the document itself too):
$addMember/$addMembers
: Adds the provided Shallow Documents as members of the document$listMembers
: Returns an array of UNRESOLVEDDocument
objects (Shallow Documents) for each member$getMembers
: Returns an array of RESOLVEDDocument
objects for each member$removeMember/$removeMembers
: Removes the specified members from the relationship (without deleting their actual documents). NOTICE: When using$removeMembers
without passing an array of members, all the members will be removed.
// ... imports
let carbonldp;
// ... initialize your CarbonLDP object
let project = {
name: "Important project",
hasMemberRelation: tasks,
isMemberOfRelation: project
};
let task1 = { name: "Task 1" };
let task2 = { name: "Task 2" };
let task3 = { name: "Task 3" };
// ... project, task1, task2 and task3 persistence (create, etc)
project.$addMembers( [ task1, task2, task3 ] )
.then( () => {
return project.$resolve();
} )
.then( ( project ) => {
console.log( project.tasks ); // Shallow Documents of task1, task2 and task3. Added because of the configured hasMemberRelation
console.log( project.tasks.indexOf( task1 ) !== - 1 ); // true (remember, shallow documents are cached and reused)
return project.$listMembers();
} )
.then( ( unresolvedTasks ) => {
console.log( unresolvedTasks ); // Shallow Documents of task1, task2 and task3
return project.$removeMember( task2 );
} )
.then( () => {
console.log( "Task 2 is no longer a member of the project" );
return project.$getMembers();
} )
.then( ( resolvedTasks ) => {
console.log( resolvedTasks ); // task1 and task3 documents
console.log( resolvedTasks[ 0 ].project ); // Shallow Document of the project. Added due to the configured isMemberOfRelation
} )
.catch( error => console.error( error ) );
// ... additional imports
import { Document } from "carbonldp/Document";
let carbonldp:CarbonLDP;
// ... initialize your CarbonLDP object
let project:Project = {
name: "Important project",
hasMemberRelation: "tasks",
isMemberOfRelation: "project"
};
let task1:Task = { name: "Task 1" }; // Being "Task" a business specific interface defined somewhere
let task2:Task = { name: "Task 2" };
let task3:Task = { name: "Task 3" };
// ... project, task1, task2 and task3 persistence (create, etc)
projectDocument.$addMembers( [ task1, task2, task3 ] )
.then( () => {
return projectDocument.$resolve<Project>();
} )
.then( ( project:Project & Document ) => {
console.log( projectDocument.tasks ); // Shallow Documents of task1, task2 and task3. Added because of the configured hasMemberRelation
console.log( projectDocument.tasks.indexOf( task1 ) !== - 1 ); // true (remember, shallow documents are cached and reused)
return projectDocument.$listMembers<Task>();
} )
.then( ( unresolvedTasks:Task & Document[] ) => {
console.log( unresolvedTasks ); // Shallow Documents of task1, task2 and task3
return projectDocument.$removeMember( task2 );
} )
.then( () => {
console.log( "Task 2 is no longer a member of the project" );
return projectDocument.$getMembers<Task>();
} )
.then( ( resolvedTasks:Task & Document ) => {
console.log( resolvedTasks ); // task1 and task3 documents
console.log( resolvedTasks[ 0 ].project ); // Shallow document of the project. Added due to the configured isMemberOfRelation
} )
.catch( error => console.error( error ) );
var carbonldp;
// ... initialize your CarbonLDP object
var project = {
name: "Important project",
hasMemberRelation: tasks,
isMemberOfRelation: project
};
var task1 = { name: "Task 1" };
var task2 = { name: "Task 2" };
var task3 = { name: "Task 3" };
// ... project, task1, task2 and task3 persistence (create, etc)
project.$addMembers( [ task1, task2, task3 ] )
.then( function() {
return project.$resolve();
} )
.then( function( project ) {
console.log( project.tasks ); // Shallow Documents of task1, task2 and task3. Added because of the configured hasMemberRelation
console.log( project.tasks.indexOf( task1 ) !== - 1 ); // true (remember, shallow documents are cached and reused)
return project.$listMembers();
} )
.then( function( unresolvedTasks ) {
console.log( unresolvedTasks ); // Shallow Documents of task1, task2 and task3
return project.$removeMember( task2 );
} )
.then( function() {
console.log( "Task 2 is no longer a member of the project" );
return project.$getMembers();
} )
.then( ( resolvedTasks ) => {
console.log( resolvedTasks ); // task1 and task3 documents
console.log( resolvedTasks[ 0 ].project ); // Shallow Documents of the project. Added due to the configured isMemberOfRelation
} )
.catch( function( error ) { console.error( error ); } );
So far we’ve seen how to configure a membership relation between documents. In this case, we used the membership relation to list tasks of a project.
Membership relations have a limitation, however: a document can only maintain one list through one property (by default members
). This means that if we now want to add people to the project, whenever we call the $addMembers
method, it would mix the people with the tasks of the project.
So, how would you add/list other documents through another property? This can be accomplished by using access points.
What is an Access Point?
Access points are special documents whose purpose is to maintain a list of members for another document. Any document can have any number of access points, removing the limitation of membership relations.
Like normal documents, access points can be customized by specifying a hasMemberRelation
and an isMemberOfRelation
.
Creating an Access Point
To create an access point for a document, you must use the create
method from the AccessPoint
class to create an in-memory AccessPoint
object. You can then pass it to the $create
method of the documents
endpoint or to a Document
object to persist it.
// ... imports
import { AccessPoint } from "carbonldp/AccessPoint";
let project;
let person1;
let person2;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
let projectMembersAccessPoint = AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( ( _projectMembersAccessPoint ) => {
console.log( projectMembersAccessPoint === _projectMembersAccessPoint ); // true
// Add members to the Access Point
return _projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( () => {
// Refresh the project so it can have the property listing the members
return project.$refresh();
} )
.then( ( refreshedProject ) => {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh();
} )
.then( ( refreshedPerson1 ) => {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Documents of project
} )
.catch( error => console.error( error ) );
// ... additional imports
import { AccessPoint, TransientAccessPoint } from "carbonldp/AccessPoint";
import { Document } from "carbonldp/Document";
let project:Project & Document;
let person1:Person & Document;
let person2:Person & Document;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
let projectMembersAccessPoint:TransientAccessPoint = AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( ( _projectMembersAccessPoint:AccessPoint ) => {
console.log( projectMembersAccessPoint === _projectMembersAccessPoint ); // true
// Add members to the Access Point
return _projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( () => {
// Refresh the project so it can have the property listing the members
return project.$refresh<Project>();
} )
.then( ( refreshedProject:Project & Document ) => {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh<Person>();
} )
.then( ( refreshedPerson1:Person & Document ) => {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Documents of project
} )
.catch( error => console.error( error ) );
// ... imports
var project;
var person1;
var person2;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
var projectMembersAccessPoint = CarbonLDP.AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( function( _projectMembersAccessPoint ) {
console.log( projectMembersAccessPoint === _projectMembersAccessPoint ); // true
// Add members to the Access Point
return _projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( function() {
// Refresh the project so it can have the property listing the members
return project.$refresh();
} )
.then( function( refreshedProject ) {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh();
} )
.then( function( refreshedPerson1 ) {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Document of project
} )
.catch( function( error ) { console.error( error ); } );
Deleting an Access Point
At the end, an access point is a document with added functionality. Because it’s a document, it behaves by the rules of a regular document, giving you access to the same methods that standard documents have.
One such method is the $delete
method of the Document class. To delete an access point you can use the $delete
method of the documents
endpoint or of a Document
object.
// ... imports
import { AccessPoint } from "carbonldp/AccessPoint";
let project;
let person1;
let person2;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
let projectMembersAccessPoint = AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( ( _projectMembersAccessPoint ) => {
console.log( projectMembersAccessPoint === _projectMembersAccessPoint ); // true
// Add members to the Access Point
return _projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( () => {
// Refresh the project so it can have the property listing the members
return project.$refresh();
} )
.then( ( refreshedProject ) => {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Delete the access point
return projectMembersAccessPoint.$delete();
} )
.then( () => {
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh();
} )
.then( ( refreshedPerson1 ) => {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Documents of project
} )
.catch( error => console.error( error ) );
// ... additional imports
import { AccessPoint, TransientAccessPoint } from "carbonldp/AccessPoint";
import { Document } from "carbonldp/Document";
let project:Project & Document;
let person1:Person & Document;
let person2:Person & Document;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
let projectMembersAccessPoint:TransientAccessPoint = AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( ( _projectMembersAccessPoint:AccessPoint ) => {
console.log( projectMembersBaseAccessPoint === _projectMembersAccessPoint ); // true
projectMembersAccessPoint = _projectMembersAccessPoint;
// Add members to the Access Point
return projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( () => {
// Refresh the project so it can have the property listing the members
return project.$refresh<Project>();
} )
.then( ( refreshedProject:Project & Document ) => {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Delete the access point
return projectMembersAccessPoint.$delete();
} )
.then( () => {
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh<Person>();
} )
.then( ( refreshedPerson1:Person & Document ) => {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Documents of project
} )
.catch( error => console.error( error ) );
// ... imports
var project;
var person1;
var person2;
// ... project, person1 and person2 document retrieval
// Creating an in-memory access point that we will persist later
var projectMembersAccessPoint = CarbonLDP.AccessPoint.create( {
hasMemberRelation: "people", // property used to store the member list in the accessPoint's document
isMemberOfRelation: "projects" // property used to link back members to the document they are members of
} );
// Persist the Access Point
project.$create( projectMembersAccessPoint, "people" );
.then( function( _projectMembersAccessPoint ) {
console.log( projectMembersAccessPoint === _projectMembersAccessPoint ); // true
// Add members to the Access Point
return _projectMembersAccessPoint.$addMembers( [ person1, person2 ] );
} )
.then( function() {
// Refresh the project so it can have the property listing the members
return project.$refresh();
} )
.then( function( refreshedProject ) {
console.log( project === refreshedProject ); // true
console.log( refreshedProject.people ); // Shallow Documents of person1 and person2
// Delete the access point
return projectMembersAccessPoint.$delete();
} )
.then( function() {
// Refresh a person so it can have the property pointing back to the document a person is member of
return person1.$refresh();
} )
.then( function( refreshedPerson1 ) {
console.log( person1 === refreshedPerson1 ); // true
console.log( refreshedPerson1.projects ); // Shallow Documents of project
} )
.catch( function( error ) { console.error( error ); } );
Because access points create soft links between documents, the member documents (person1 and person2) will still exist, but the property that linked them back to the document they were members of will be deleted from them.
Conclusion
Overall, there are two types of links between documents: hard links (direct parent-child relation) and soft links (document referencing other documents). You can set a soft link relationship with the membership relation. You can configure the membership relation by using the hasMemberRelation and the isMemberOfRelation.
A document can only maintain one list through one property. If you want to add more members through another property you can use access points. Access points are special documents whose purpose is to maintain a list of members for another document. Because access points are documents with added functionality, they can still use the same methods as any document, so you can $create
or $delete
them using the documents
endpoint or the Document
.