├── .gitignore ├── .travis.yml ├── .travis_publish ├── solid-architecture-v1-0-0.tex ├── solid-architecture-v1-1-0.tex ├── solid-architecture-v1-2-0.tex ├── solid-architecture-v1-3-0.tex ├── store-atomicity.tex └── tikz-uml.sty /.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.bbl 3 | *.blg 4 | *.dvi 5 | *.fdb* 6 | *.fls 7 | *.log 8 | *.out 9 | *.pdf 10 | *.svg 11 | *.synctex* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: minimal 2 | 3 | addons: 4 | apt: 5 | packages: 6 | - texlive-latex-base 7 | - texlive-fonts-recommended 8 | - texlive-latex-extra 9 | - texlive-fonts-extra 10 | - texlive-latex-recommended 11 | - texlive-extra-utils 12 | - latex-xcolor 13 | - pgf 14 | 15 | script: 16 | - for file in *.tex; do 17 | pdflatex -interaction=nonstopmode -halt-on-error $file; 18 | done 19 | 20 | after_success: 21 | - ./.travis_publish 22 | -------------------------------------------------------------------------------- /.travis_publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | REPO_NAME="RubenVerborgh/solid-server-architecture" 3 | PUBLICATION_BRANCH=gh-pages 4 | 5 | # Only publish from the main repository's master branch 6 | if [ "$TRAVIS_REPO_SLUG" != "$REPO_NAME" ] || [ "$TRAVIS_BRANCH" != "master" ] || [ "$TRAVIS_PULL_REQUEST" != "false" ]; then exit; fi 7 | echo -e "Building $PUBLICATION_BRANCH...\n" 8 | 9 | # Checkout the branch 10 | REPO_PATH=$PWD 11 | pushd $HOME 12 | git clone --quiet --depth=1 --branch=$PUBLICATION_BRANCH https://${GH_TOKEN}@github.com/$REPO_NAME publish 2>&1 > /dev/null 13 | cd publish 14 | 15 | # Don't update if already at the latest version 16 | if [[ `git log -1 --pretty=%B` == *$TRAVIS_COMMIT* ]]; then exit; fi 17 | 18 | # Update pages 19 | rm -r * 2> /dev/null 20 | cp -r $REPO_PATH/*.pdf . 21 | 22 | # Commit and push latest version 23 | git add . 24 | git config user.name "Travis" 25 | git config user.email "travis@travis-ci.org" 26 | git commit -m "Update to $TRAVIS_COMMIT." 27 | git push -fq origin $PUBLICATION_BRANCH 2>&1 > /dev/null 28 | popd 29 | -------------------------------------------------------------------------------- /solid-architecture-v1-0-0.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | % Page setup 5 | \usepackage[a3paper,landscape,margin=2cm]{geometry} 6 | 7 | % Typography 8 | \usepackage[scaled]{berasans} 9 | \renewcommand*\familydefault{\sfdefault} 10 | \usepackage[T1]{fontenc} 11 | \parindent 0pt 12 | 13 | % TikZ 14 | \usepackage{./tikz-uml} 15 | \usetikzlibrary{positioning} 16 | 17 | % Headings 18 | \makeatletter 19 | \def\@maketitle{% 20 | {\LARGE\bf\@title\par} 21 | \vskip .5em 22 | {\large\@author\ -- \@date\par} 23 | \vskip 2em 24 | } 25 | \makeatother 26 | 27 | % Document metadata 28 | \title{ 29 | Solid server -- Proposed architecture v1.0.0 30 | \it (status: superseded) 31 | } 32 | \author{Ruben Verborgh} 33 | \date{May 9, 2019} 34 | 35 | \begin{document} 36 | 37 | \maketitle 38 | 39 | \section*{LDP and Access Control} 40 | 41 | \begin{tikzpicture} 42 | 43 | \begin{umlpackage}{HTTP} 44 | \umlinterface[]{HttpHandler}{}{ 45 | + canHandle(HttpRequest) : Promise\\ 46 | + handle(HttpRequest, HttpResponse) : void\\ 47 | } 48 | 49 | \umlclass[right=1 of HttpHandler]{HttpServer}{}{ 50 | + HttpServer(Array)\\ 51 | + listen(port : int) : void\\ 52 | + handle(HttpRequest, HttpResponse) : void\\ 53 | } 54 | \umlaggreg[mult=*]{HttpServer}{HttpHandler} 55 | \end{umlpackage} 56 | 57 | \begin{umlpackage}[y=-4.5]{LDP} 58 | \umlclass{LdpHandler}{}{ 59 | + LdpHandler(OperationFactory)\\ 60 | } 61 | \umlimpl{LdpHandler}{HttpHandler} 62 | 63 | 64 | \umlsimpleclass[right=2 of LdpHandler.north east,anchor=south west]{TargetExtractor} 65 | \umlaggreg[mult=1]{LdpHandler}{TargetExtractor} 66 | 67 | \umlsimpleclass[right=2 of TargetExtractor]{ResourceIdentifier} 68 | \umldep[arg=creates,pos=0.5]{TargetExtractor}{ResourceIdentifier} 69 | 70 | 71 | \umlsimpleclass[below=0.25 of TargetExtractor.south east,anchor=north east]{BodyParser} 72 | \umlaggreg[mult=*]{LdpHandler}{BodyParser} 73 | 74 | \umlsimpleclass[below=0.25 of ResourceIdentifier.south west,anchor=north west]{Representation} 75 | \umldep[arg=creates,pos=0.5]{BodyParser}{Representation} 76 | 77 | \umlsimpleclass[right=1 of Representation]{Patch} 78 | \umlinherit{Patch}{Representation} 79 | 80 | 81 | \umlsimpleclass[below=0.25 of BodyParser.south east,anchor=north east]{PreferenceParser} 82 | \umlaggreg[mult=1]{LdpHandler}{PreferenceParser} 83 | 84 | \umlsimpleclass[below=0.25 of Representation.south west,anchor=north west]{RepresentationPreferences} 85 | \umldep[arg=creates,pos=0.5]{PreferenceParser}{RepresentationPreferences} 86 | 87 | 88 | \umlsimpleclass[below=0.25 of PreferenceParser.south east,anchor=north east]{ResponseWriter} 89 | \umlaggreg[mult=1]{LdpHandler}{ResponseWriter} 90 | 91 | \umlsimpleclass[below=0.25 of RepresentationPreferences.south west,anchor=north west]{HttpResponse} 92 | \umldep[arg=writes,pos=0.5]{ResponseWriter}{HttpResponse} 93 | 94 | 95 | \begin{umlpackage}[x=6,y=-4]{Operations} 96 | \umlclass[]{OperationFactory}{}{ 97 | + OperationFactory(ResourceStore)\\ 98 | + createOperation(method : string, ResourceIdentifier,\\* 99 | RepresentationPreferences, \ldots) : Operation\\ 100 | } 101 | \umlaggreg[mult=1,geometry=|-]{LdpHandler}{OperationFactory} 102 | 103 | \umlinterface[below=1 of OperationFactory]{Operation}{ 104 | + target : ResourceIdentifier\\ 105 | + requestBody : Representation?\\ 106 | + preferences : RepresentationPreferences\\ 107 | + requiredPermissions : PermissionSet\\ 108 | }{ 109 | + execute() : Promise\\ 110 | } 111 | \umldep[arg=creates,pos=0.5]{OperationFactory}{Operation} 112 | 113 | \umlsimpleclass[right=1 of Operation.north east,anchor=north west]{GetOperation} 114 | \umlsimpleclass[below=.25 of GetOperation.south west,anchor=north west]{PostOperation} 115 | \umlsimpleclass[below=.25 of PostOperation.south west,anchor=north west]{PutOperation} 116 | \umlsimpleclass[below=.25 of PutOperation.south west,anchor=north west]{PatchOperation} 117 | \umlimpl{GetOperation}{Operation} 118 | \umlimpl{PostOperation}{Operation} 119 | \umlimpl{PutOperation}{Operation} 120 | \umlimpl{PatchOperation}{Operation} 121 | 122 | \end{umlpackage} 123 | \end{umlpackage} 124 | 125 | \begin{umlpackage}{Storage} 126 | \umlinterface[below=2.25 of Operation]{ResourceStore}{}{ 127 | + getRepresentation(ResourceIdentifier, RepresentationPreferences) : Promise\\ 128 | + addResource(container : ResourceIdentifier, Representation) : Promise\\ 129 | + setRepresentation(ResourceIdentifier, Representation) : Promise\\ 130 | + deleteResource(ResourceIdentifier) : Promise\\ 131 | + modifyResource(ResourceIdentifier, Patch) : Promise\\ 132 | } 133 | \umlaggreg[mult=1]{Operation}{ResourceStore} 134 | \end{umlpackage} 135 | 136 | \begin{umlpackage}{Authentication} 137 | \umlinterface[left=2 of LdpHandler.west]{CredentialsExtractor}{}{ 138 | + extractCredentials(HttpRequest) : Promise\\ 139 | } 140 | \umlaggreg[mult=1]{LdpHandler}{CredentialsExtractor} 141 | 142 | \umlsimpleclass[below=1 of CredentialsExtractor]{Credentials} 143 | \umldep[arg=creates,pos=0.5]{CredentialsExtractor}{Credentials} 144 | \end{umlpackage} 145 | 146 | \begin{umlpackage}{Authorization} 147 | \umlinterface[below=4.5 of CredentialsExtractor]{Authorizer}{}{ 148 | + hasPermissions(Credentials, 149 | ResourceIdentifier,\\* 150 | PermissionSet) : Promise\\ 151 | } 152 | \umldep[arg=uses,pos=0.3]{Authorizer}{Credentials} 153 | \umlaggreg[mult=1,geometry=|-,weight=0.6]{LdpHandler}{Authorizer} 154 | 155 | \umlsimpleclass[below=1 of Authorizer]{AclBasedAuthorizer} 156 | \umlimpl{AclBasedAuthorizer}{Authorizer} 157 | \umlaggreg[mult=1,geometry=|-]{AclBasedAuthorizer}{ResourceStore} 158 | \end{umlpackage} 159 | 160 | \end{tikzpicture} 161 | 162 | \end{document} 163 | -------------------------------------------------------------------------------- /solid-architecture-v1-1-0.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | % Page setup 5 | \usepackage[a3paper,landscape,margin=2cm]{geometry} 6 | 7 | % Typography 8 | \usepackage[T1]{fontenc} 9 | \usepackage[scaled]{berasans} 10 | \usepackage[scaled]{beramono} 11 | \renewcommand*\familydefault{\sfdefault} 12 | \usepackage{microtype} 13 | \parindent 0pt 14 | 15 | % TikZ 16 | \usepackage{./tikz-uml} 17 | \usetikzlibrary{positioning} 18 | 19 | % Headings 20 | \makeatletter 21 | \def\@maketitle{% 22 | {\LARGE\bf\@title\par} 23 | \vskip .5em 24 | {\large\@author\ -- \@date\par} 25 | \vskip 2em 26 | } 27 | \makeatother 28 | 29 | % Notes 30 | \usepackage{multicol} 31 | \newenvironment{Note} 32 | {\begin{multicols}{3}% 33 | \parskip 1em} 34 | {\end{multicols}} 35 | 36 | % Document metadata 37 | \title{ 38 | Solid server -- Proposed architecture v1.1.0 39 | \it (status: superseded) 40 | } 41 | \author{Ruben Verborgh} 42 | \date{July 14, 2019} 43 | 44 | \begin{document} 45 | 46 | \maketitle 47 | 48 | 49 | \section*{Purpose} 50 | This document conveys a~personal view 51 | on important architectural considerations for a~Solid server. 52 | 53 | It is intended as a~tool for discussion, 54 | to raise questions, 55 | and to highlight concerns. 56 | 57 | It does not have any official standing whatsoever. 58 | 59 | 60 | \section*{Legend} 61 | The architectural diagram follows standard UML notation. 62 | 63 | For more specific symbols that are not part of UML, 64 | Node.js/JavaScript/TypeScript conventions were used as follows: 65 | 66 | \begin{description} 67 | \item[T?] represents a~value that is either not present 68 | or a~value of type~T. 69 | \item[Promise] represents a~value that will asynchronously resolve 70 | to a~value of type~T. 71 | \item[Readable] represents an asynchronous one-time readable stream 72 | of values of type~T. 73 | \item[Buffer] is an in-memory buffer of bytes, 74 | possibly with a~character encoding. 75 | \end{description} 76 | 77 | 78 | \newcommand\ResourceStoreBody{% 79 | + getRepresentation(ResourceIdentifier, RepresentationPreferences) : Promise\\ 80 | + addResource(container : ResourceIdentifier, Representation) : Promise\\ 81 | + setRepresentation(ResourceIdentifier, Representation) : Promise\\ 82 | + deleteResource(ResourceIdentifier) : Promise\\ 83 | + modifyResource(ResourceIdentifier, Patch) : Promise\\ 84 | } 85 | 86 | \section*{Overview of LDP and Access Control} 87 | \begin{tikzpicture} 88 | 89 | \begin{umlpackage}{HTTP} 90 | \umlinterface[]{HttpHandler}{}{ 91 | + canHandle(HttpRequest) : Promise\\ 92 | + handle(HttpRequest, HttpResponse) : void\\ 93 | } 94 | 95 | \umlclass[right=1 of HttpHandler]{HttpServer}{}{ 96 | + HttpServer(Array)\\ 97 | + listen(port : int) : void\\ 98 | + handle(HttpRequest, HttpResponse) : void\\ 99 | } 100 | \umluniaggreg[mult=*]{HttpServer}{HttpHandler} 101 | \end{umlpackage} 102 | 103 | \begin{umlpackage}[y=-4.5]{LDP} 104 | \umlclass{LdpHandler}{}{ 105 | + LdpHandler(OperationFactory)\\ 106 | } 107 | \umlimpl{LdpHandler}{HttpHandler} 108 | 109 | 110 | \umlsimpleclass[right=2 of LdpHandler.north east,anchor=south west]{TargetExtractor} 111 | \umluniaggreg[mult=1,anchor2=west]{LdpHandler}{TargetExtractor} 112 | 113 | \umlsimpleclass[right=2 of TargetExtractor]{ResourceIdentifier} 114 | \umldep[arg=creates,pos=0.5]{TargetExtractor}{ResourceIdentifier} 115 | 116 | 117 | \umlsimpleclass[below=0.25 of TargetExtractor.south east,anchor=north east]{BodyParser} 118 | \umluniaggreg[mult=*,anchor2=west]{LdpHandler}{BodyParser} 119 | 120 | \umlsimpleclass[below=0.25 of ResourceIdentifier.south west,anchor=north west]{Representation} 121 | \umldep[arg=creates,pos=0.5]{BodyParser}{Representation} 122 | 123 | \umlsimpleclass[right=1 of Representation]{Patch} 124 | \umlinherit{Patch}{Representation} 125 | 126 | 127 | \umlsimpleclass[below=0.25 of BodyParser.south east,anchor=north east]{PreferenceParser} 128 | \umluniaggreg[mult=1,anchor2=west]{LdpHandler}{PreferenceParser} 129 | 130 | \umlsimpleclass[below=0.25 of Representation.south west,anchor=north west]{RepresentationPreferences} 131 | \umldep[arg=creates,pos=0.5]{PreferenceParser}{RepresentationPreferences} 132 | 133 | 134 | \umlsimpleclass[below=0.25 of PreferenceParser.south east,anchor=north east]{ResponseWriter} 135 | \umluniaggreg[mult=1,anchor1=south east,anchor2=west]{LdpHandler}{ResponseWriter} 136 | 137 | \umlsimpleclass[below=0.25 of RepresentationPreferences.south west,anchor=north west]{HttpResponse} 138 | \umldep[arg=writes,pos=0.5]{ResponseWriter}{HttpResponse} 139 | 140 | 141 | \begin{umlpackage}[x=6,y=-4]{Operations} 142 | \umlclass[]{OperationFactory}{}{ 143 | + OperationFactory(ResourceStore)\\ 144 | + createOperation(method : string, ResourceIdentifier,\\* 145 | RepresentationPreferences, \ldots) : Operation\\ 146 | } 147 | \umluniaggreg[mult=1,geometry=|-]{LdpHandler}{OperationFactory} 148 | 149 | \umlinterface[below=1 of OperationFactory]{Operation}{ 150 | + target : ResourceIdentifier\\ 151 | + requestBody : Representation?\\ 152 | + preferences : RepresentationPreferences\\ 153 | + requiredPermissions : PermissionSet\\ 154 | }{ 155 | + execute() : Promise\\ 156 | } 157 | \umldep[arg=creates,pos=0.5]{OperationFactory}{Operation} 158 | 159 | \umlsimpleclass[right=1 of Operation.north east,anchor=north west]{GetOperation} 160 | \umlsimpleclass[below=.25 of GetOperation.south west,anchor=north west]{PostOperation} 161 | \umlsimpleclass[below=.25 of PostOperation.south west,anchor=north west]{PutOperation} 162 | \umlsimpleclass[below=.25 of PutOperation.south west,anchor=north west]{PatchOperation} 163 | \umlimpl[anchor1=west]{GetOperation}{Operation} 164 | \umlimpl[anchor1=west]{PostOperation}{Operation} 165 | \umlimpl[anchor1=west]{PutOperation}{Operation} 166 | \umlimpl[anchor1=west]{PatchOperation}{Operation} 167 | 168 | \end{umlpackage} 169 | \end{umlpackage} 170 | 171 | \begin{umlpackage}{Storage} 172 | \umlinterface[below=2.25 of Operation]{ResourceStore}{}{ 173 | \ResourceStoreBody 174 | } 175 | \umluniaggreg[mult=1]{Operation}{ResourceStore} 176 | \end{umlpackage} 177 | 178 | \begin{umlpackage}{Authentication} 179 | \umlinterface[left=2 of LdpHandler.west]{CredentialsExtractor}{}{ 180 | + extractCredentials(HttpRequest) : Promise\\ 181 | } 182 | \umluniaggreg[mult=1]{LdpHandler}{CredentialsExtractor} 183 | 184 | \umlsimpleclass[below=1 of CredentialsExtractor]{Credentials} 185 | \umldep[arg=creates,pos=0.5]{CredentialsExtractor}{Credentials} 186 | \end{umlpackage} 187 | 188 | \begin{umlpackage}{Authorization} 189 | \umlinterface[below=4.5 of CredentialsExtractor]{Authorizer}{}{ 190 | + hasPermissions(Credentials, 191 | ResourceIdentifier,\\* 192 | PermissionSet) : Promise\\ 193 | } 194 | \umldep[arg=uses,pos=0.3]{Authorizer}{Credentials} 195 | \umluniaggreg[mult=1,geometry=|-,weight=0.6]{LdpHandler}{Authorizer} 196 | 197 | \umlsimpleclass[below=1 of Authorizer]{AclBasedAuthorizer} 198 | \umlimpl{AclBasedAuthorizer}{Authorizer} 199 | \umluniaggreg[mult=1,geometry=|-]{AclBasedAuthorizer}{ResourceStore} 200 | \end{umlpackage} 201 | 202 | \end{tikzpicture} 203 | 204 | \clearpage 205 | 206 | \section*{Resources and Representations} 207 | \begin{Note} 208 | The intention of \textbf{ResourceIdentifier} and \textbf{Representation} 209 | is to capture the REST notion of a~resource and its representation. 210 | In~the~case of a~photograph, 211 | the resource is the photograph itself, 212 | whereas a~representation is a~concrete manifestation of that photograph 213 | with a~certain resolution and file~type. 214 | In~the~case of an RDF document, 215 | the~resource is the RDF graph, 216 | and concrete representations serialize that graph 217 | into Turtle or specific framings of~JSON-LD. 218 | \columnbreak 219 | 220 | For all practical purposes, 221 | \textbf{ResourceIdentifier} can just be a~\textbf{URL}; 222 | the terminology is mainly used to emphasize 223 | the resource/representation notion of REST. 224 | Also, there is no \textbf{Resource} class, 225 | because resources are always manipulated through representations in REST, 226 | so we only need to \emph{identify} resources, 227 | and only deal with them through their representations. 228 | \columnbreak 229 | 230 | Crucially, as the diagram below shows, 231 | the \textbf{Representation} interface 232 | can have vastly different underlying in-memory structures, 233 | such as strings, binary streams, RDF streams, etc. 234 | So they can be photographs as well as RDF streams, 235 | and most other classes handling them do not need to care. 236 | This enables back-ends to be RDF-aware when they need to, 237 | and RDF-oblivious when they do~not. 238 | \end{Note} 239 | 240 | \bigskip 241 | 242 | \begin{tikzpicture} 243 | \umlinterface{Representation}{ 244 | + identifier : ResourceIdentifier?\\ 245 | + metadata : RepresentationMetadata\\ 246 | + data : Readable\\ 247 | + dataType : String\\ 248 | }{} 249 | 250 | \umlinterface[left=2 of Representation]{ResourceIdentifier}{ 251 | }{} 252 | \umluniaggreg[mult=0..1]{Representation}{ResourceIdentifier} 253 | 254 | \umlinterface[below=2 of Representation]{RepresentationMetadata}{ 255 | }{ 256 | + raw : Array\\ 257 | + byteSize : int?\\ 258 | + contentType : String?\\ 259 | + encoding : String?\\ 260 | + language : String?\\ 261 | + dateTime : Date?\\ 262 | + profiles : Array\\ 263 | } 264 | \umluniaggreg[mult=1]{Representation}{RepresentationMetadata} 265 | 266 | \umlclass[right=2 of Representation.north east]{BinaryRepresentation}{ 267 | + data : Readable\\ 268 | }{} 269 | \umlimpl{BinaryRepresentation}{Representation} 270 | 271 | \umlclass[right=2 of Representation.south east]{QuadRepresentation}{ 272 | + data : Readable\\ 273 | }{} 274 | \umlimpl{QuadRepresentation}{Representation} 275 | 276 | \end{tikzpicture} 277 | 278 | \bigskip 279 | 280 | \begin{Note} 281 | The \textbf{dataType} field returns the name of the class 282 | that elements of the \textbf{data} readable stream will have, 283 | for instance, 284 | \verb!Buffer! or \verb!Quad!. 285 | \columnbreak 286 | 287 | Based on the \textbf{dataType} and \textbf{metadata} fields, 288 | other components can decide whether or not the representation 289 | is acceptable to the user agent, 290 | and, if this is not the case, 291 | convert to a~format that is. 292 | For instance, 293 | a~\verb!text/turtle! stream is acceptable 294 | for a~user agent that requested \verb!text/*!, 295 | whereas a~\verb!Readable! will still require serialization. 296 | 297 | \columnbreak 298 | The \textbf{RepresentationMetadata} interface 299 | essentially exposes a~set of RDF triples 300 | that describe properties about the representation. 301 | For convenience, 302 | direct getters to common properties can be added, 303 | non-binding examples of which are shown in the diagram. 304 | 305 | \end{Note} 306 | 307 | \clearpage 308 | 309 | \section*{ResourceStore} 310 | \begin{tikzpicture} 311 | \umlinterface{ResourceStore}{}{\ResourceStoreBody} 312 | 313 | 314 | % FileSystemStore 315 | 316 | \umlclass[right=2 of ResourceStore]{FileSystemStore}{ 317 | - ResourceMapper mapper\\ 318 | }{} 319 | \umlimpl{FileSystemStore}{ResourceStore} 320 | 321 | \umlinterface[right=1 of FileSystemStore]{ResourceMapper}{}{ 322 | + mapFilePathToUrl(File): Promise<\{URL, RepresentationMetadata\}>\\ 323 | + mapUrlToFilePath(URL, RepresentationMetadata): Promise\\ 324 | } 325 | \umluniaggreg[mult=1]{FileSystemStore}{ResourceMapper} 326 | 327 | 328 | % KeyValueStore 329 | 330 | \umlabstract[above=1 of FileSystemStore.north west,anchor=south west]{KeyValueStore}{}{ 331 | \# get(ResourceIdentifier) : Promise\\ 332 | \# replace(ResourceIdentifier, BinaryRepresentation) : Promise\\ 333 | \# delete(ResourceIdentifier) : Promise\\ 334 | } 335 | \umlimpl[anchor1=west,anchor2=north east]{KeyValueStore}{ResourceStore} 336 | 337 | \umlsimpleclass[right=1 of KeyValueStore.north east]{RedisStore} 338 | \umlinherit{RedisStore}{KeyValueStore} 339 | 340 | \umlsimpleclass[right=1 of KeyValueStore.east]{CassandraStore} 341 | \umlinherit{CassandraStore}{KeyValueStore} 342 | 343 | 344 | % TripleStore 345 | 346 | \umlclass[below=1 of FileSystemStore.south west,anchor=north west]{TripleStore}{}{ 347 | + getRepresentation(\ldots) : Promise\\ 348 | } 349 | \umlimpl[anchor1=west,anchor2=south east]{TripleStore}{ResourceStore} 350 | 351 | \umlclass[right=1 of TripleStore]{SparqlEndpointStore}{ 352 | - endpoint: URL\\ 353 | }{} 354 | \umlinherit{SparqlEndpointStore}{TripleStore} 355 | 356 | 357 | % RepresentationConverter 358 | 359 | \umlclass[below=1 of TripleStore.south west,anchor=north west]{RepresentationConvertingStore}{ 360 | - source : ResourceStore\\ 361 | - converters : Array\\ 362 | }{} 363 | \umlimpl[anchor1=west,anchor2=-30,geometry=-|]{RepresentationConvertingStore}{ResourceStore} 364 | 365 | \umlinterface[right=1 of RepresentationConvertingStore]{RepresentationConverter}{}{ 366 | + supports(Representation, RepresentationPreferences): Promise\\ 367 | + convert(Representation, RepresentationPreferences): Promise\\ 368 | } 369 | \umluniaggreg[mult=*]{RepresentationConvertingStore}{RepresentationConverter} 370 | 371 | 372 | % CompositeResourceStore 373 | 374 | \umlclass[below=1 of RepresentationConvertingStore.south west,anchor=north west]{CompositeResourceStore}{ 375 | - sources : Map\\ 376 | }{} 377 | \umlimpl[anchor1=173,anchor2=-40,geometry=-|]{CompositeResourceStore}{ResourceStore} 378 | \umlunicompo[mult=*,anchor1=187,anchor2=-60,geometry=-|]{CompositeResourceStore}{ResourceStore} 379 | 380 | \end{tikzpicture} 381 | 382 | \bigskip 383 | 384 | \begin{Note} 385 | A~\textbf{ResourceStore} will \emph{try} to satisfy 386 | any \textbf{RepresentationPreferences} passed to it, 387 | but only if this is reasonably easy for the store in question. 388 | For instance, a~SPARQL endpoint can typically 389 | generate N-Triples as easily as Turtle, 390 | so it makes sense to directly generate N-Triples 391 | if the client prefers this. 392 | On the other hand, 393 | a~file system will typically only have one representation on disk, 394 | so it is fine to always serve that single representation, 395 | regardless of client preferences. 396 | \columnbreak 397 | 398 | Optionally, a~\textbf{RepresentationConvertingStore} can be used 399 | to satisfy client preferences more accurately. 400 | It has access to \textbf{RepresentationConverter} instances, 401 | which could (for instance) convert a~stream of quads 402 | into Turtle or a~specific JSON-LD frame. 403 | It can decorate any existing \textbf{ResourceStore} 404 | to extend it with more kinds of representations 405 | such as different content types. 406 | \columnbreak 407 | 408 | A~\textbf{CompositeResourceStore} can be used 409 | to have multiple back-ends on one pod, 410 | each answering to different URL patterns. 411 | This mechanism \emph{could} be used 412 | also to serve large files like images, 413 | or static assets such as apps or scripts. 414 | \end{Note} 415 | 416 | \clearpage 417 | 418 | \section*{Patch} 419 | \begin{Note} 420 | A~\textbf{Patch} contains a~description 421 | of changes to be made to a~certain 422 | (representation of a) resource. 423 | The \textbf{Patch} object itself 424 | does not know how to \emph{apply} this patch; 425 | it is merely a~data object. 426 | \columnbreak 427 | 428 | \null 429 | \columnbreak 430 | 431 | \null 432 | \end{Note} 433 | 434 | \bigskip 435 | 436 | \begin{tikzpicture} 437 | \umlinterface{Patch}{}{} 438 | 439 | \umlclass[right=2 of Patch.north east,anchor=south west]{LineBasedPatch}{ 440 | + deletions : Map\\ 441 | + additions : Map\\ 442 | }{} 443 | \umlimpl[anchors=west and north east]{LineBasedPatch}{Patch} 444 | 445 | \umlclass[below=1 of LineBasedPatch.south west,anchor=north west]{GraphPatternPatch}{ 446 | + where : Array\\ 447 | + delete : Array\\ 448 | + insert : Array\\ 449 | }{} 450 | \umlimpl[anchor2=east]{GraphPatternPatch}{Patch} 451 | 452 | \umlclass[below=1 of GraphPatternPatch.south west,anchor=north west]{BinaryPatch}{}{} 453 | \umlimpl[anchors=north west and south east]{BinaryPatch}{Patch} 454 | 455 | \umlclass[below=1 of BinaryPatch.south west,anchor=north west]{ImageFilter}{}{} 456 | \umlimpl[anchors=north west and south]{ImageFilter}{Patch} 457 | \end{tikzpicture} 458 | 459 | \begin{Note} 460 | A~\textbf{ResourceStore} \emph{might} have knowledge 461 | on how to apply certain types of patches itself. 462 | For instance, file-based stores 463 | might have built-in support for \textbf{LineBasedPatch}, 464 | and SPARQL endpoints or in-memory RDF stores 465 | likely have built-in support for \textbf{GraphPatternPatch}. 466 | \columnbreak 467 | 468 | There is case to be made for a~\emph{Patcher} interface 469 | for objects that can apply all patches of a~certain type 470 | to certain representations. 471 | For instance, 472 | a~\textbf{GraphPatternPatch} could be applied 473 | to RDF graphs serialized as documents, 474 | by a~\emph{GraphPatternPatcher} 475 | that operates independently of any specific store. 476 | 477 | \columnbreak 478 | 479 | \null 480 | \end{Note} 481 | 482 | \end{document} 483 | -------------------------------------------------------------------------------- /solid-architecture-v1-2-0.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | % Page setup 5 | \usepackage[a3paper,landscape,margin=2cm]{geometry} 6 | 7 | % Typography 8 | \usepackage[T1]{fontenc} 9 | \usepackage[scaled]{berasans} 10 | \usepackage[scaled]{beramono} 11 | \renewcommand*\familydefault{\sfdefault} 12 | \usepackage{microtype} 13 | \parindent 0pt 14 | 15 | \newcommand\component[1]{\mbox{\bf #1}} 16 | \newcommand\field[1]{\mbox{\tt #1}} 17 | 18 | % TikZ 19 | \usepackage{./tikz-uml} 20 | \usetikzlibrary{positioning} 21 | 22 | % Headings 23 | \makeatletter 24 | \def\@maketitle{% 25 | {\LARGE\bf\@title\par} 26 | \vskip .5em 27 | {\large\@author\ -- \@date\par} 28 | \vskip 2em 29 | } 30 | \makeatother 31 | 32 | % Notes 33 | \usepackage{multicol} 34 | \newenvironment{Note} 35 | {\begin{multicols}{3}% 36 | \parskip 1em} 37 | {\end{multicols}} 38 | 39 | % Lists 40 | \usepackage{enumitem} 41 | \setlist{nosep,topsep=-1em} 42 | 43 | % Document metadata 44 | \title{ 45 | Solid server -- Selected architectural diagrams v1.2.0 46 | \it (status: proposal) 47 | } 48 | \author{Ruben Verborgh} 49 | 50 | \begin{document} 51 | 52 | \maketitle 53 | 54 | 55 | \section*{Purpose} 56 | This document conveys views 57 | on important architectural considerations for a~Solid server. 58 | 59 | It is mainly intended as a~tool for discussing, 60 | raising questions, 61 | and highlighting concerns. 62 | 63 | 64 | \section*{Legend} 65 | The architectural diagram follows standard UML notation. 66 | 67 | For more specific symbols that are not part of UML, 68 | Node.js/JavaScript/TypeScript conventions were used as follows: 69 | 70 | \begin{description} 71 | \item[T?] represents a~value that is either not present 72 | or a~value of type~T. 73 | \item[Promise] represents a~value that will asynchronously resolve 74 | to a~value of type~T. 75 | \item[Readable] represents an asynchronous one-time readable stream 76 | of values of type~T. 77 | \item[Buffer] is an in-memory buffer of bytes, 78 | possibly with a~character encoding. 79 | \end{description} 80 | 81 | 82 | \newcommand\ResourceStoreBody{% 83 | + getRepresentation(ResourceIdentifier, RepresentationPreferences, Conditions?) : Promise\\ 84 | + addResource(container : ResourceIdentifier, Representation, Conditions?) : Promise\\ 85 | + setRepresentation(ResourceIdentifier, Representation, Conditions?) : Promise\\ 86 | + deleteResource(ResourceIdentifier, Conditions?) : Promise\\ 87 | + modifyResource(ResourceIdentifier, Patch, Conditions?) : Promise\\ 88 | } 89 | 90 | \section*{Overview of LDP and Access Control} 91 | \begin{tikzpicture} 92 | 93 | \begin{umlpackage}{HTTP} 94 | \umlinterface[]{HttpHandler}{}{ 95 | + canHandle(HttpRequest) : Promise\\ 96 | + handle(HttpRequest, HttpResponse) : void\\ 97 | } 98 | 99 | \umlclass[right=1 of HttpHandler]{HttpServer}{}{ 100 | + HttpServer(Array)\\ 101 | + listen(port : int) : void\\ 102 | + handle(HttpRequest, HttpResponse) : void\\ 103 | } 104 | \umluniaggreg[mult=*]{HttpServer}{HttpHandler} 105 | \end{umlpackage} 106 | 107 | \begin{umlpackage}[y=-4.5]{LDP} 108 | \umlclass{LdpHandler}{}{ 109 | + LdpHandler(OperationFactory)\\ 110 | } 111 | \umlimpl{LdpHandler}{HttpHandler} 112 | 113 | 114 | \umlsimpleclass[right=2 of LdpHandler.north east,anchor=south west]{TargetExtractor} 115 | \umluniaggreg[mult=1,anchor2=west]{LdpHandler}{TargetExtractor} 116 | 117 | \umlsimpleclass[right=2 of TargetExtractor]{ResourceIdentifier} 118 | \umldep[arg=creates,pos=0.5]{TargetExtractor}{ResourceIdentifier} 119 | 120 | 121 | \umlsimpleclass[below=0.25 of TargetExtractor.south east,anchor=north east]{BodyParser} 122 | \umluniaggreg[mult=*,anchor2=west]{LdpHandler}{BodyParser} 123 | 124 | \umlsimpleclass[below=0.25 of ResourceIdentifier.south west,anchor=north west]{Representation} 125 | \umldep[arg=creates,pos=0.5]{BodyParser}{Representation} 126 | 127 | \umlsimpleclass[right=1 of Representation]{Patch} 128 | \umlinherit{Patch}{Representation} 129 | 130 | 131 | \umlsimpleclass[below=0.25 of BodyParser.south east,anchor=north east]{PreferenceParser} 132 | \umluniaggreg[mult=1,anchor2=west]{LdpHandler}{PreferenceParser} 133 | 134 | \umlsimpleclass[below=0.25 of Representation.south west,anchor=north west]{RepresentationPreferences} 135 | \umldep[arg=creates,pos=0.5]{PreferenceParser}{RepresentationPreferences} 136 | 137 | 138 | \umlsimpleclass[below=0.25 of PreferenceParser.south east,anchor=north east]{ResponseWriter} 139 | \umluniaggreg[mult=1,anchor1=south east,anchor2=west]{LdpHandler}{ResponseWriter} 140 | 141 | \umlsimpleclass[below=0.25 of RepresentationPreferences.south west,anchor=north west]{HttpResponse} 142 | \umldep[arg=writes,pos=0.5]{ResponseWriter}{HttpResponse} 143 | 144 | 145 | \begin{umlpackage}[x=6,y=-4]{Operations} 146 | \umlclass[]{OperationFactory}{}{ 147 | + OperationFactory(ResourceStore)\\ 148 | + createOperation(method : string, ResourceIdentifier,\\* 149 | RepresentationPreferences, \ldots) : Operation\\ 150 | } 151 | \umluniaggreg[mult=1,geometry=|-]{LdpHandler}{OperationFactory} 152 | 153 | \umlinterface[below=1 of OperationFactory]{Operation}{ 154 | + target : ResourceIdentifier\\ 155 | + requestBody : Representation?\\ 156 | + preferences : RepresentationPreferences\\ 157 | + requiredPermissions : PermissionSet\\ 158 | }{ 159 | + execute() : Promise\\ 160 | } 161 | \umldep[arg=creates,pos=0.5]{OperationFactory}{Operation} 162 | 163 | \umlsimpleclass[right=1 of Operation.north east,anchor=north west]{GetOperation} 164 | \umlsimpleclass[below=.25 of GetOperation.south west,anchor=north west]{PostOperation} 165 | \umlsimpleclass[below=.25 of PostOperation.south west,anchor=north west]{PutOperation} 166 | \umlsimpleclass[below=.25 of PutOperation.south west,anchor=north west]{PatchOperation} 167 | \umlimpl[anchor1=west]{GetOperation}{Operation} 168 | \umlimpl[anchor1=west]{PostOperation}{Operation} 169 | \umlimpl[anchor1=west]{PutOperation}{Operation} 170 | \umlimpl[anchor1=west]{PatchOperation}{Operation} 171 | 172 | \end{umlpackage} 173 | \end{umlpackage} 174 | 175 | \begin{umlpackage}{Storage} 176 | \umlinterface[below=2.25 of Operation]{ResourceStore}{}{ 177 | \ResourceStoreBody 178 | } 179 | \umluniaggreg[mult=1]{Operation}{ResourceStore} 180 | \end{umlpackage} 181 | 182 | \begin{umlpackage}{Authentication} 183 | \umlinterface[left=2 of LdpHandler.west]{CredentialsExtractor}{}{ 184 | + extractCredentials(HttpRequest) : Promise\\ 185 | } 186 | \umluniaggreg[mult=1]{LdpHandler}{CredentialsExtractor} 187 | 188 | \umlsimpleclass[below=1 of CredentialsExtractor]{Credentials} 189 | \umldep[arg=creates,pos=0.5]{CredentialsExtractor}{Credentials} 190 | \end{umlpackage} 191 | 192 | \begin{umlpackage}{Authorization} 193 | \umlinterface[below=4.5 of CredentialsExtractor]{Authorizer}{}{ 194 | + ensurePermissions(Credentials, 195 | ResourceIdentifier,\\* 196 | PermissionSet) : Promise\\ 197 | } 198 | \umldep[arg=uses,pos=0.3]{Authorizer}{Credentials} 199 | \umluniaggreg[mult=1,geometry=|-,weight=0.6]{LdpHandler}{Authorizer} 200 | 201 | \umlsimpleclass[below=1 of Authorizer]{AclBasedAuthorizer} 202 | \umlimpl{AclBasedAuthorizer}{Authorizer} 203 | \umluniaggreg[mult=1,geometry=|-]{AclBasedAuthorizer}{ResourceStore} 204 | \end{umlpackage} 205 | 206 | \end{tikzpicture} 207 | 208 | \clearpage 209 | 210 | \section*{Resources and Representations} 211 | \begin{Note} 212 | The intention of \component{ResourceIdentifier} and \component{Representation} 213 | is to capture the REST notion of a~resource and its representation. 214 | In~the~case of a~photograph, 215 | the resource is the photograph itself, 216 | whereas a~representation is a~concrete manifestation of that photograph 217 | with a~certain resolution and file~type. 218 | In~the~case of an RDF document, 219 | the~resource is the RDF graph, 220 | and concrete representations serialize that graph 221 | into Turtle or specific framings of~JSON-LD. 222 | \columnbreak 223 | 224 | For all practical purposes, 225 | \component{ResourceIdentifier} can just be a~\component{URL}; 226 | the terminology is mainly used to emphasize 227 | the resource/representation notion of REST. 228 | Also, there is no \component{Resource} class, 229 | because resources are always manipulated through representations in REST, 230 | so we only need to \emph{identify} resources, 231 | and only deal with them through their representations. 232 | \columnbreak 233 | 234 | Crucially, as the diagram below shows, 235 | the \component{Representation} interface 236 | can have vastly different underlying in-memory structures, 237 | such as strings, binary streams, RDF streams, etc. 238 | So they can be photographs as well as RDF streams, 239 | and most other classes handling them do not need to care. 240 | This enables back-ends to be RDF-aware when they need to, 241 | and RDF-oblivious when they do~not. 242 | \end{Note} 243 | 244 | \bigskip 245 | 246 | \begin{tikzpicture} 247 | \umlinterface{Representation}{ 248 | + identifier : ResourceIdentifier?\\ 249 | + metadata : RepresentationMetadata\\ 250 | + data : Readable\\ 251 | + dataType : String\\ 252 | }{} 253 | 254 | \umlinterface[left=2 of Representation]{ResourceIdentifier}{ 255 | }{} 256 | \umluniaggreg[mult=0..1]{Representation}{ResourceIdentifier} 257 | 258 | \umlinterface[below=2 of Representation]{RepresentationMetadata}{ 259 | }{ 260 | + raw : Array\\ 261 | + byteSize : int?\\ 262 | + contentType : String?\\ 263 | + encoding : String?\\ 264 | + language : String?\\ 265 | + dateTime : Date?\\ 266 | + profiles : Array\\ 267 | } 268 | \umluniaggreg[mult=1]{Representation}{RepresentationMetadata} 269 | 270 | \umlclass[right=2 of Representation.north east]{BinaryRepresentation}{ 271 | + data : Readable\\ 272 | }{} 273 | \umlimpl{BinaryRepresentation}{Representation} 274 | 275 | \umlclass[right=2 of Representation.south east]{QuadRepresentation}{ 276 | + data : Readable\\ 277 | }{} 278 | \umlimpl{QuadRepresentation}{Representation} 279 | 280 | \end{tikzpicture} 281 | 282 | \bigskip 283 | 284 | \begin{Note} 285 | The \field{dataType} field returns the name of the class 286 | that elements of the \field{data} readable stream will have, 287 | for instance, 288 | \component{Buffer} or \component{Quad}. 289 | \columnbreak 290 | 291 | Based on the \field{dataType} and \field{metadata} fields, 292 | other components can decide whether or not the representation 293 | is acceptable to the user agent, 294 | and, if this is not the case, 295 | convert to a~format that is. 296 | For instance, 297 | a~\verb!text/turtle! stream is acceptable 298 | for a~user agent that requested \verb!text/*!, 299 | whereas a~\component{Readable} will still require serialization. 300 | 301 | \columnbreak 302 | The \component{RepresentationMetadata} interface 303 | essentially exposes a~set of RDF triples 304 | that describe properties about the representation. 305 | For convenience, 306 | direct getters to common properties can be added, 307 | non-binding examples of which are shown in the diagram. 308 | 309 | \end{Note} 310 | 311 | \clearpage 312 | 313 | \section*{ResourceStore implementations} 314 | \begin{tikzpicture} 315 | \umlinterface{ResourceStore}{}{\ResourceStoreBody} 316 | 317 | 318 | % FileSystemStore 319 | 320 | \umlclass[right=2 of ResourceStore]{FileSystemStore}{ 321 | - ResourceMapper mapper\\ 322 | }{} 323 | \umlimpl{FileSystemStore}{ResourceStore} 324 | 325 | \umlinterface[right=1 of FileSystemStore]{ResourceMapper}{}{ 326 | + mapFilePathToUrl(File): Promise<\{URL, RepresentationMetadata\}>\\ 327 | + mapUrlToFilePath(URL, RepresentationMetadata): Promise\\ 328 | } 329 | \umluniaggreg[mult=1]{FileSystemStore}{ResourceMapper} 330 | 331 | 332 | % KeyValueStore 333 | 334 | \umlabstract[above=1 of FileSystemStore.north west,anchor=south west]{KeyValueStore}{}{ 335 | \# get(ResourceIdentifier) : Promise\\ 336 | \# replace(ResourceIdentifier, BinaryRepresentation) : Promise\\ 337 | \# delete(ResourceIdentifier) : Promise\\ 338 | } 339 | \umlimpl[anchor1=west,anchor2=north east]{KeyValueStore}{ResourceStore} 340 | 341 | \umlsimpleclass[right=1 of KeyValueStore.north east]{RedisStore} 342 | \umlinherit[anchor1=west]{RedisStore}{KeyValueStore} 343 | 344 | \umlsimpleclass[right=1 of KeyValueStore.east]{CassandraStore} 345 | \umlinherit[anchor1=west]{CassandraStore}{KeyValueStore} 346 | 347 | 348 | % TripleStore 349 | 350 | \umlclass[below=1 of FileSystemStore.south west,anchor=north west]{TripleStore}{}{ 351 | + getRepresentation(\ldots) : Promise\\ 352 | } 353 | \umlimpl[anchor1=west,anchor2=south east]{TripleStore}{ResourceStore} 354 | 355 | \umlclass[right=1 of TripleStore]{SparqlEndpointStore}{ 356 | - endpoint: URL\\ 357 | }{} 358 | \umlinherit{SparqlEndpointStore}{TripleStore} 359 | 360 | 361 | % RepresentationConverter 362 | 363 | \umlclass[below=1 of TripleStore.south west,anchor=north]{RepresentationConvertingStore}{ 364 | - source : ResourceStore\\ 365 | - converters : Array\\ 366 | }{} 367 | \umlimpl[anchor1=west,anchor2=-18,geometry=-|]{RepresentationConvertingStore}{ResourceStore} 368 | 369 | \umlinterface[right=1 of RepresentationConvertingStore]{RepresentationConverter}{}{ 370 | + supports(Representation, RepresentationPreferences): Promise\\ 371 | + convert(Representation, RepresentationPreferences): Promise\\ 372 | } 373 | \umluniaggreg[mult=*]{RepresentationConvertingStore}{RepresentationConverter} 374 | 375 | 376 | % CompositeResourceStore 377 | 378 | \umlclass[below=1 of RepresentationConvertingStore.south west,anchor=north west]{CompositeResourceStore}{ 379 | - sources : Map\\ 380 | }{} 381 | \umlimpl[anchor1=173,anchor2=-22,geometry=-|]{CompositeResourceStore}{ResourceStore} 382 | \umlunicompo[mult=*,anchor1=190,anchor2=-28,geometry=-|]{CompositeResourceStore}{ResourceStore} 383 | 384 | \end{tikzpicture} 385 | 386 | \bigskip 387 | 388 | \begin{Note} 389 | A~\component{ResourceStore} will \emph{try} to satisfy 390 | any \component{RepresentationPreferences} passed to it, 391 | but only if this is reasonably easy for the store in question. 392 | For instance, a~SPARQL endpoint can typically 393 | generate N-Triples as easily as Turtle, 394 | so it makes sense to directly generate N-Triples 395 | if the client prefers this. 396 | On the other hand, 397 | a~file system will typically only have one representation on disk, 398 | so it is fine to always serve that single representation, 399 | regardless of client preferences. 400 | \columnbreak 401 | 402 | Optionally, a~\component{RepresentationConvertingStore} can be used 403 | to satisfy client preferences more accurately. 404 | It has access to \component{RepresentationConverter} instances, 405 | which could (for instance) convert a~stream of quads 406 | into Turtle or a~specific JSON-LD frame. 407 | It can decorate any existing \component{ResourceStore} 408 | to extend it with more kinds of representations 409 | such as different content types. 410 | \columnbreak 411 | 412 | A~\component{CompositeResourceStore} can be used 413 | to have multiple back-ends on one pod, 414 | each answering to different URL patterns. 415 | This mechanism \emph{could} be used 416 | also to serve large files like images, 417 | or static assets such as apps or scripts. 418 | \end{Note} 419 | 420 | \section*{ResourceStore atomicity and conditional requests} 421 | \begin{tikzpicture} 422 | \umlinterface{ResourceStore}{}{\ResourceStoreBody} 423 | 424 | \umlclass[below=1 of ResourceStore]{Conditions}{ 425 | + matchesEtag : string[]\\ 426 | + notMatchesEtag : string[]\\ 427 | + modifiedSince: date?\\ 428 | + unmodifiedSince: date?\\ 429 | }{ 430 | + matches(metadata : RepresentationMetadata): boolean\\ 431 | + matches(eTag : string?, lastModified : date?): boolean\\ 432 | } 433 | \umldep[arg=uses,pos=0.5]{ResourceStore}{Conditions} 434 | 435 | \umlinterface[right=2 of ResourceStore.north east, anchor=south west]{AtomicResourceStore}{}{ 436 | } 437 | \umlimpl{AtomicResourceStore}{ResourceStore} 438 | 439 | \umlclass[right=2 of ResourceStore.east]{LockingResourceStore}{ 440 | - ResourceStore source\\ 441 | - ResourceLocker locks\\ 442 | }{ 443 | } 444 | \umlimpl{LockingResourceStore}{ResourceStore} 445 | \umlimpl{LockingResourceStore}{AtomicResourceStore} 446 | \umlunicompo[mult=1,anchor1=202,anchor2=-6]{LockingResourceStore}{ResourceStore} 447 | 448 | \umlinterface[right=2 of LockingResourceStore.east]{ResourceLocker}{ 449 | }{ 450 | + acquire(ResourceIdentifier) : Promise\\ 451 | } 452 | \umluniaggreg[mult=1]{LockingResourceStore}{ResourceLocker} 453 | 454 | \umlinterface[below=1 of ResourceLocker.south]{Lock}{ 455 | }{ 456 | + release() : Promise\\ 457 | } 458 | \umldep[arg=creates,pos=0.5]{ResourceLocker}{Lock} 459 | 460 | \end{tikzpicture} 461 | 462 | \bigskip 463 | 464 | \begin{Note} 465 | The \component{ResourceStore} interface has been designed 466 | such that each of its methods \emph{can} be implemented 467 | in an atomic way: 468 | for each CRUD operation, 469 | only one dedicated method needs to be called. 470 | A~fifth method enables the optimization of partial updates 471 | with \verb!PATCH!. 472 | It is up to the implementer of the interface 473 | to (not) make an implementation atomic. 474 | For some implementations, 475 | such as triple stores or other database back-ends, 476 | atomicity is a~given. 477 | We~\emph{could} explicitly indicate atomicity 478 | by having such implementations 479 | implement the (otherwise empty) \component{AtomicResourceStore} interface 480 | as a~tag. 481 | 482 | \columnbreak 483 | 484 | Some back-ends are not atomic by themselves, 485 | such as a~file system, 486 | where a~read+append sequence could unknowingly 487 | be interrupted by a~write 488 | that thereby breaks atomicity. 489 | Instead of having to implement 490 | a~dedicated locking mechanism for every non-atomic back-end, 491 | these stores can be made atomic 492 | by decorating them with a~\component{LockingResourceStore}. 493 | This class wraps another \component{ResourceStore} 494 | and adds a~locking mechanism, 495 | of which different implementations can exist. 496 | 497 | \columnbreak 498 | 499 | It is important to emphasize 500 | that atomicity is \emph{not} the only reason 501 | for the design of the \component{ResourceStore} interface. 502 | Another consideration is \field{modifyResource}, 503 | which allows us to optimize modifications in a~backend-specific way. 504 | Since we expect small modifications to larger resources 505 | to be a~common for Solid apps, 506 | we need to be able to handle those efficiently. 507 | \field{modifyResource} gives implementations the freedom 508 | on how to apply patches, 509 | such that they can pick whichever option is most efficient 510 | for a~given patch 511 | and, if desired, support atomicity. 512 | 513 | \end{Note} 514 | 515 | \begin{Note} 516 | The \component{Conditions} class 517 | represents the conditions of an HTTP conditional request. 518 | It~is passed to all write methods 519 | (and possibly also read) of \component{ResourceStore}. 520 | The~store is responsible for validating conditions 521 | at the right moment 522 | and, should validation fail, 523 | for aborting the modification by throwing an error. 524 | 525 | \columnbreak 526 | If the store knows how to validate conditions, 527 | it can use the raw exposed fields on \component{Conditions}. 528 | If it does not, 529 | it can call \field{modifyResource} 530 | with both ETag and the last modified date, 531 | or try one of them before the other. 532 | Finally, 533 | if it knows about neither ETag nor last modified date, 534 | it can pass the metadata as a~whole. 535 | 536 | \columnbreak 537 | The conditions argument is optional, 538 | and only passed for conditional requests. 539 | If a~store decides not to support conditional requests, 540 | it must throw an error if~conditions are passed. 541 | 542 | \end{Note} 543 | 544 | \clearpage 545 | 546 | \section*{Patch} 547 | \begin{Note} 548 | A~\component{Patch} contains a~description 549 | of changes to be made to a~certain 550 | (representation of a) resource. 551 | The \component{Patch} object itself 552 | does not know how to \emph{apply} this patch; 553 | it is merely a~data object. 554 | \columnbreak 555 | 556 | \null 557 | \columnbreak 558 | 559 | \null 560 | \end{Note} 561 | 562 | \bigskip 563 | 564 | \begin{tikzpicture} 565 | \umlinterface{Patch}{}{} 566 | 567 | \umlclass[right=2 of Patch.north east,anchor=south west]{LineBasedPatch}{ 568 | + deletions : Map\\ 569 | + additions : Map\\ 570 | }{} 571 | \umlimpl[anchors=west and north east]{LineBasedPatch}{Patch} 572 | 573 | \umlclass[below=1 of LineBasedPatch.south west,anchor=north west]{GraphPatternPatch}{ 574 | + where : Array\\ 575 | + delete : Array\\ 576 | + insert : Array\\ 577 | }{} 578 | \umlimpl[anchor2=east]{GraphPatternPatch}{Patch} 579 | 580 | \umlclass[below=1 of GraphPatternPatch.south west,anchor=north west]{BinaryPatch}{}{} 581 | \umlimpl[anchors=north west and south east]{BinaryPatch}{Patch} 582 | 583 | \umlclass[below=1 of BinaryPatch.south west,anchor=north west]{ImageFilter}{}{} 584 | \umlimpl[anchors=north west and south]{ImageFilter}{Patch} 585 | \end{tikzpicture} 586 | 587 | \begin{Note} 588 | A~\component{ResourceStore} \emph{might} have knowledge 589 | on how to apply certain types of patches itself. 590 | For instance, file-based stores 591 | might have built-in support for \component{LineBasedPatch}, 592 | and SPARQL endpoints or in-memory RDF stores 593 | likely have built-in support for \component{GraphPatternPatch}. 594 | \columnbreak 595 | 596 | There is case to be made for a~\component{Patcher} interface 597 | for objects that can apply all patches of a~certain type 598 | to certain representations. 599 | For instance, 600 | a~\component{GraphPatternPatch} could be applied 601 | to RDF graphs serialized as documents, 602 | by a~\component{GraphPatternPatcher} 603 | that operates independently of any specific store. 604 | 605 | \columnbreak 606 | 607 | \null 608 | \end{Note} 609 | 610 | \clearpage 611 | 612 | \section*{Quota} 613 | \begin{tikzpicture} 614 | \umlsimpleclass[]{AdministrationApi}{}{} 615 | \umlinterface[right=2 of AdministrationApi]{HttpHandler}{}{} 616 | \umlimpl{AdministrationApi}{HttpHandler} 617 | 618 | \umlinterface[below=2 of AdministrationApi]{SizeReporter}{}{ 619 | + getSize(ResourceIdentifier) : Promise\\ 620 | } 621 | \umldep[arg=uses,pos=0.5]{AdministrationApi}{SizeReporter} 622 | 623 | \umlclass[right=2 of SizeReporter]{SizeCache}{ 624 | - source : ResourceStore\\ 625 | - reporter : SizeReporter\\ 626 | }{} 627 | \umlimpl{SizeCache}{SizeReporter} 628 | \umlunicompo[mult=1,anchor1=202,anchor2=-12]{SizeCache}{SizeReporter} 629 | 630 | \umlinterface[below=1.5 of SizeCache]{ResourceStore}{}{} 631 | \umlimpl{SizeCache}{ResourceStore} 632 | \umlunicompo[mult=1,anchor1=-124,anchor2=128]{SizeCache}{ResourceStore} 633 | 634 | \umlsimpleclass[below=2 of SizeReporter]{FileSystemStore} 635 | \umlimpl{FileSystemStore}{ResourceStore} 636 | \umlimpl{FileSystemStore}{SizeReporter} 637 | 638 | \end{tikzpicture} 639 | 640 | \begin{Note} 641 | Storage quota can be retrieved 642 | through the \component{AdministrationApi}, 643 | which is an independent \component{HttpHandler} 644 | that accesses a~\component{SizeReporter}. 645 | The \component{SizeReporter} interface 646 | can be implemented by stores such as \component{FileSystemStore}. 647 | 648 | \columnbreak 649 | Since computing quota can be expensive, 650 | a~\component{SizeCache} 651 | could maintain quota for subpaths, 652 | which it invalidates upon write operations. 653 | 654 | \columnbreak 655 | \null 656 | 657 | \end{Note} 658 | 659 | \clearpage 660 | 661 | \section*{ACL caching} 662 | \begin{tikzpicture} 663 | \umlclass{AclBasedAuthorizer}{ 664 | - source : AclCache\\ 665 | }{ 666 | + ensurePermissions(Credentials, 667 | ResourceIdentifier,\\* 668 | PermissionSet) : Promise\\ 669 | } 670 | 671 | \umlclass[right=2 of AclBasedAuthorizer]{AclCache}{ 672 | - source : ResourceStore\\ 673 | }{} 674 | \umldep[arg=uses,pos=0.5]{AclBasedAuthorizer}{AclCache} 675 | 676 | \umlinterface[below=1.5 of AclCache]{ResourceStore}{}{} 677 | \umlimpl{AclCache}{ResourceStore} 678 | \umlunicompo[mult=1,anchor1=-130,anchor2=128]{AclCache}{ResourceStore} 679 | 680 | \end{tikzpicture} 681 | 682 | \begin{Note} 683 | Since ACLs will be used frequently, 684 | we need a~mechanism for caching them. 685 | Importantly, we need a~way to \emph{invalidate} the cache 686 | every time a~write operation happens 687 | to ACLs that can affect a~given document. 688 | 689 | \columnbreak 690 | To this end, 691 | the \component{AclCache} will wrap around a~\component{ResourceStore} 692 | and intercept all write requests, 693 | such that it can invalidate parts of its cache 694 | when writes to ACL~documents arrive. 695 | 696 | \columnbreak 697 | \null 698 | 699 | \end{Note} 700 | 701 | \end{document} 702 | -------------------------------------------------------------------------------- /solid-architecture-v1-3-0.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | % Page setup 5 | \usepackage[a3paper,landscape,margin=2cm]{geometry} 6 | 7 | % Typography 8 | \usepackage[T1]{fontenc} 9 | \usepackage[scaled]{berasans} 10 | \usepackage[scaled]{beramono} 11 | \renewcommand*\familydefault{\sfdefault} 12 | \usepackage{microtype} 13 | \parindent 0pt 14 | 15 | \newcommand\component[1]{\mbox{\bf #1}} 16 | \newcommand\field[1]{\mbox{\tt #1}} 17 | 18 | % TikZ 19 | \usepackage{./tikz-uml} 20 | \usetikzlibrary{positioning} 21 | 22 | % Headings 23 | \makeatletter 24 | \def\@maketitle{% 25 | {\LARGE\bf\@title\par} 26 | \vskip .5em 27 | {\large\@author\ -- \@date\par} 28 | \vskip 2em 29 | } 30 | \makeatother 31 | 32 | % Notes 33 | \usepackage{multicol} 34 | \newenvironment{Note} 35 | {\begin{multicols}{3}% 36 | \parskip 1em} 37 | {\end{multicols}} 38 | 39 | % Lists 40 | \usepackage{enumitem} 41 | \setlist{nosep,topsep=-1em} 42 | 43 | % Document metadata 44 | \title{ 45 | Solid server -- Selected architectural diagrams v1.3.0 46 | \it (status: proposal) 47 | } 48 | \author{Ruben Verborgh} 49 | 50 | \begin{document} 51 | 52 | \maketitle 53 | 54 | 55 | \section*{Purpose} 56 | This document conveys views 57 | on important architectural considerations for a~Solid server. 58 | 59 | It is mainly intended as a~tool for discussing, 60 | raising questions, 61 | and highlighting concerns. 62 | 63 | 64 | \section*{Legend} 65 | The architectural diagram follows standard UML notation. 66 | 67 | For more specific symbols that are not part of UML, 68 | Node.js/JavaScript/TypeScript conventions were used as follows: 69 | 70 | \begin{description} 71 | \item[T?] represents a~value that is either not present 72 | or a~value of type~T. 73 | \item[Promise] represents a~value that will asynchronously resolve 74 | to a~value of type~T. 75 | \item[Readable] represents an asynchronous one-time readable stream 76 | of values of type~T. 77 | \item[Buffer] is an in-memory buffer of bytes, 78 | possibly with a~character encoding. 79 | \end{description} 80 | 81 | \section*{Overview of LDP and Access Control} 82 | 83 | \newcommand\ResourceStoreBody{% 84 | + getRepresentation(ResourceIdentifier, RepresentationPreferences, Conditions?) : Promise\\ 85 | + addResource(container : ResourceIdentifier, Representation, Conditions?) : Promise\\ 86 | + deleteResource(ResourceIdentifier, Conditions?) : Promise\\ 87 | + setRepresentation(ResourceIdentifier, Representation, Conditions?) : Promise\\ 88 | + modifyRepresentation(ResourceIdentifier, Patch, Conditions?) : Promise\\ 89 | } 90 | 91 | \begin{tikzpicture} 92 | 93 | \begin{umlpackage}{Server} 94 | \umlinterface[]{HttpHandler}{}{ 95 | + canHandle(HttpRequest) : Promise\\ 96 | + handle(HttpRequest, HttpResponse) : Promise\\ 97 | } 98 | 99 | \umlclass[right=1 of HttpHandler]{HttpServer}{}{ 100 | + HttpServer(Array)\\ 101 | + listen(port : int) : void\\ 102 | + handle(HttpRequest, HttpResponse) : void\\ 103 | } 104 | \umluniaggreg[mult=*]{HttpServer}{HttpHandler} 105 | \end{umlpackage} 106 | 107 | \begin{umlpackage}[y=-5]{LDP} 108 | \umlclass{AuthenticatedLdpHandler}{}{ 109 | } 110 | \umlimpl{AuthenticatedLdpHandler}{HttpHandler} 111 | 112 | \begin{umlpackage}[x=7]{HTTP} 113 | \umlsimpleclass[right=1 of AuthenticatedLdpHandler.north east]{ResponseWriter} 114 | \umluniaggreg[mult=1,above,anchor1=10,anchor2=west,geometry=-|-,arm1=0.5]{AuthenticatedLdpHandler}{ResponseWriter} 115 | \umlsimpleclass[right=2 of ResponseWriter.east,anchor=west]{HttpResponse} 116 | \umldep[arg=writes,pos=0.5,below]{ResponseWriter}{HttpResponse} 117 | 118 | \umlclass[below=.5 of ResponseWriter.south east,anchor=north east]{RequestParser}{}{ 119 | } 120 | \umluniaggreg[mult=1,below,anchor1=-10,anchor2=west,geometry=-|-,arm1=0.5]{AuthenticatedLdpHandler}{RequestParser} 121 | 122 | \umlsimpleclass[right=2 of RequestParser.east,anchor=south west]{TargetExtractor} 123 | \umluniaggreg[arg2=1,above,anchor1=north east,anchor2=west,geometry=-|-]{RequestParser}{TargetExtractor} 124 | \umlsimpleclass[right=2 of TargetExtractor]{ResourceIdentifier} 125 | \umldep[arg=creates,pos=0.5]{TargetExtractor}{ResourceIdentifier} 126 | 127 | \umlsimpleclass[below=0.25 of TargetExtractor.south east,anchor=north east]{BodyParser} 128 | \umluniaggreg[mult=*,anchor1=east,anchor2=west,geometry=-|-]{RequestParser}{BodyParser} 129 | \umlsimpleclass[below=0.25 of ResourceIdentifier.south west,anchor=north west]{Representation} 130 | \umldep[arg=creates,pos=0.5]{BodyParser}{Representation} 131 | \umlsimpleclass[right=1 of Representation]{Patch} 132 | \umlinherit{Patch}{Representation} 133 | 134 | \umlsimpleclass[below=0.25 of BodyParser.south east,anchor=north east]{PreferenceParser} 135 | \umluniaggreg[mult=1,anchor1=south east,anchor2=west,geometry=-|-]{RequestParser}{PreferenceParser} 136 | \umlsimpleclass[below=0.25 of Representation.south west,anchor=north west]{RepresentationPreferences} 137 | \umldep[arg=creates,pos=0.5]{PreferenceParser}{RepresentationPreferences} 138 | \end{umlpackage} 139 | 140 | 141 | \begin{umlpackage}[x=7,y=-9]{Operations} 142 | \umlinterface[]{OperationHandler}{}{ 143 | + canHandle(Operation) : Promise\\ 144 | + handle(Operation) : Promise\\ 145 | } 146 | \umluniaggreg[anchor1=310,mult=1,geometry=|-]{AuthenticatedLdpHandler}{OperationHandler} 147 | 148 | \umlclass[above=1 of OperationHandler]{Operation}{ 149 | + method : String\\ 150 | + target : ResourceIdentifier\\ 151 | + body : Representation?\\ 152 | + preferences : RepresentationPreferences\\ 153 | }{} 154 | \umldep[arg=uses,pos=0.5]{OperationHandler}{Operation} 155 | 156 | \umlsimpleclass[right=1 of OperationHandler.north east,anchor=south west]{CompositeOperationHandler} 157 | \umlimpl[anchor1=177,anchor2=14]{CompositeOperationHandler}{OperationHandler} 158 | \umlunicompo[anchor1=182,anchor2=10]{CompositeOperationHandler}{OperationHandler} 159 | 160 | \umlsimpleclass[below=.5 of CompositeOperationHandler.south west,anchor=north west]{GetOperationHandler} 161 | \umlsimpleclass[below=.25 of GetOperationHandler.south west,anchor=north west]{PostOperationHandler} 162 | \umlsimpleclass[below=.25 of PostOperationHandler.south west,anchor=north west]{PutOperationHandler} 163 | \umlsimpleclass[below=.25 of PutOperationHandler.south west,anchor=north west]{PatchOperationHandler} 164 | \umlimpl[anchor1=west]{GetOperationHandler}{OperationHandler} 165 | \umlimpl[anchor1=west]{PostOperationHandler}{OperationHandler} 166 | \umlimpl[anchor1=west]{PutOperationHandler}{OperationHandler} 167 | \umlimpl[anchor1=west]{PatchOperationHandler}{OperationHandler} 168 | \end{umlpackage} 169 | 170 | \umldep[arg=creates,pos=0.5,left,geometry=|-|,arm1=-1.5]{RequestParser}{Operation} 171 | \end{umlpackage} 172 | 173 | \begin{umlpackage}{Storage} 174 | \umlinterface[below=4 of OperationHandler]{ResourceStore}{}{ 175 | \ResourceStoreBody 176 | } 177 | \umluniaggreg[mult=1]{OperationHandler}{ResourceStore} 178 | \end{umlpackage} 179 | 180 | \begin{umlpackage}{Authentication} 181 | \umlinterface[left=2 of AuthenticatedLdpHandler.west]{CredentialsExtractor}{}{ 182 | + extractCredentials(HttpRequest) : Promise\\ 183 | } 184 | \umluniaggreg[mult=1]{AuthenticatedLdpHandler}{CredentialsExtractor} 185 | 186 | \umlsimpleclass[below=1 of CredentialsExtractor]{Credentials} 187 | \umldep[arg=creates,pos=0.5]{CredentialsExtractor}{Credentials} 188 | \end{umlpackage} 189 | 190 | \begin{umlpackage}{Authorization} 191 | \umlinterface[below=4 of CredentialsExtractor]{Authorizer}{}{ 192 | + ensurePermissions(Credentials, 193 | ResourceIdentifier,\\* 194 | PermissionSet) : Promise\\ 195 | } 196 | \umldep[arg=uses,pos=0.3]{Authorizer}{Credentials} 197 | \umluniaggreg[anchor1=230,mult=1,geometry=|-,weight=0.6]{AuthenticatedLdpHandler}{Authorizer} 198 | 199 | \umlsimpleclass[below=1 of Authorizer]{AclBasedAuthorizer} 200 | \umlimpl{AclBasedAuthorizer}{Authorizer} 201 | \umluniaggreg[mult=1,geometry=|-]{AclBasedAuthorizer}{ResourceStore} 202 | \end{umlpackage} 203 | 204 | \end{tikzpicture} 205 | 206 | \clearpage 207 | 208 | \section*{Resources and Representations} 209 | \begin{Note} 210 | The intention of \component{ResourceIdentifier} and \component{Representation} 211 | is to capture the REST notion of a~resource and its representation. 212 | In~the~case of a~photograph, 213 | the resource is the photograph itself, 214 | whereas a~representation is a~concrete manifestation of that photograph 215 | with a~certain resolution and file~type. 216 | In~the~case of an RDF document, 217 | the~resource is the RDF graph, 218 | and concrete representations serialize that graph 219 | into Turtle or specific framings of~JSON-LD. 220 | \columnbreak 221 | 222 | For all practical purposes, 223 | \component{ResourceIdentifier} can just be a~\component{URL}; 224 | the terminology is mainly used to emphasize 225 | the resource/representation notion of REST. 226 | Also, there is no \component{Resource} class, 227 | because resources are always manipulated through representations in REST, 228 | so we only need to \emph{identify} resources, 229 | and only deal with them through their representations. 230 | \columnbreak 231 | 232 | Crucially, as the diagram below shows, 233 | the \component{Representation} interface 234 | can have vastly different underlying in-memory structures, 235 | such as strings, binary streams, RDF streams, etc. 236 | So they can be photographs as well as RDF streams, 237 | and most other classes handling them do not need to care. 238 | This enables back-ends to be RDF-aware when they need to, 239 | and RDF-oblivious when they do~not. 240 | \end{Note} 241 | 242 | \bigskip 243 | 244 | \begin{tikzpicture} 245 | \umlinterface{Representation}{ 246 | + identifier : ResourceIdentifier?\\ 247 | + metadata : RepresentationMetadata\\ 248 | + data : Readable\\ 249 | + dataType : String\\ 250 | }{} 251 | 252 | \umlinterface[left=2 of Representation]{ResourceIdentifier}{ 253 | }{} 254 | \umluniaggreg[mult=0..1]{Representation}{ResourceIdentifier} 255 | 256 | \umlinterface[below=2 of Representation]{RepresentationMetadata}{ 257 | }{ 258 | + raw : Array\\ 259 | + byteSize : int?\\ 260 | + contentType : String?\\ 261 | + encoding : String?\\ 262 | + language : String?\\ 263 | + dateTime : Date?\\ 264 | + profiles : Array\\ 265 | } 266 | \umluniaggreg[mult=1]{Representation}{RepresentationMetadata} 267 | 268 | \umlclass[right=2 of Representation.north east]{BinaryRepresentation}{ 269 | + data : Readable\\ 270 | }{} 271 | \umlimpl{BinaryRepresentation}{Representation} 272 | 273 | \umlclass[right=2 of Representation.south east]{QuadRepresentation}{ 274 | + data : Readable\\ 275 | }{} 276 | \umlimpl{QuadRepresentation}{Representation} 277 | 278 | \end{tikzpicture} 279 | 280 | \bigskip 281 | 282 | \begin{Note} 283 | The \field{dataType} field returns the name of the class 284 | that elements of the \field{data} readable stream will have, 285 | for instance, 286 | \component{Buffer} or \component{Quad}. 287 | \columnbreak 288 | 289 | Based on the \field{dataType} and \field{metadata} fields, 290 | other components can decide whether or not the representation 291 | is acceptable to the user agent, 292 | and, if this is not the case, 293 | convert to a~format that is. 294 | For instance, 295 | a~\verb!text/turtle! stream is acceptable 296 | for a~user agent that requested \verb!text/*!, 297 | whereas a~\component{Readable} will still require serialization. 298 | 299 | \columnbreak 300 | The \component{RepresentationMetadata} interface 301 | essentially exposes a~set of RDF triples 302 | that describe properties about the representation. 303 | For convenience, 304 | direct getters to common properties can be added, 305 | non-binding examples of which are shown in the diagram. 306 | 307 | \end{Note} 308 | 309 | \clearpage 310 | 311 | \section*{ResourceStore implementations} 312 | \begin{tikzpicture} 313 | \umlinterface{ResourceStore}{}{\ResourceStoreBody} 314 | 315 | 316 | % FileSystemStore 317 | 318 | \umlclass[right=2 of ResourceStore]{FileSystemStore}{ 319 | - ResourceMapper mapper\\ 320 | }{} 321 | \umlimpl{FileSystemStore}{ResourceStore} 322 | 323 | \umlinterface[right=1 of FileSystemStore]{ResourceMapper}{}{ 324 | + mapFilePathToUrl(File): Promise<\{URL, RepresentationMetadata\}>\\ 325 | + mapUrlToFilePath(URL, RepresentationMetadata): Promise\\ 326 | } 327 | \umluniaggreg[mult=1]{FileSystemStore}{ResourceMapper} 328 | 329 | 330 | % KeyValueStore 331 | 332 | \umlabstract[above=1 of FileSystemStore.north west,anchor=south west]{KeyValueStore}{}{ 333 | \# get(ResourceIdentifier) : Promise\\ 334 | \# replace(ResourceIdentifier, BinaryRepresentation) : Promise\\ 335 | \# delete(ResourceIdentifier) : Promise\\ 336 | } 337 | \umlimpl[anchor1=west,anchor2=north east]{KeyValueStore}{ResourceStore} 338 | 339 | \umlsimpleclass[right=1 of KeyValueStore.north east]{RedisStore} 340 | \umlinherit[anchor1=west]{RedisStore}{KeyValueStore} 341 | 342 | \umlsimpleclass[right=1 of KeyValueStore.east]{CassandraStore} 343 | \umlinherit[anchor1=west]{CassandraStore}{KeyValueStore} 344 | 345 | 346 | % TripleStore 347 | 348 | \umlclass[below=1 of FileSystemStore.south west,anchor=north west]{TripleStore}{}{ 349 | + getRepresentation(\ldots) : Promise\\ 350 | } 351 | \umlimpl[anchor1=west,anchor2=south east]{TripleStore}{ResourceStore} 352 | 353 | \umlclass[right=1 of TripleStore]{SparqlEndpointStore}{ 354 | - endpoint: URL\\ 355 | }{} 356 | \umlinherit{SparqlEndpointStore}{TripleStore} 357 | 358 | 359 | % RepresentationConverter 360 | 361 | \umlclass[below=1 of TripleStore.south west,anchor=north]{RepresentationConvertingStore}{ 362 | - source : ResourceStore\\ 363 | - converters : Array\\ 364 | }{} 365 | \umlimpl[anchor1=west,anchor2=-18,geometry=-|]{RepresentationConvertingStore}{ResourceStore} 366 | 367 | \umlinterface[right=1 of RepresentationConvertingStore]{RepresentationConverter}{}{ 368 | + supports(Representation, RepresentationPreferences): Promise\\ 369 | + convert(Representation, RepresentationPreferences): Promise\\ 370 | } 371 | \umluniaggreg[mult=*]{RepresentationConvertingStore}{RepresentationConverter} 372 | 373 | 374 | % CompositeResourceStore 375 | 376 | \umlclass[below=1 of RepresentationConvertingStore.south west,anchor=north west]{CompositeResourceStore}{ 377 | - sources : Map\\ 378 | }{} 379 | \umlimpl[anchor1=173,anchor2=-22,geometry=-|]{CompositeResourceStore}{ResourceStore} 380 | \umlunicompo[mult=*,anchor1=190,anchor2=-28,geometry=-|]{CompositeResourceStore}{ResourceStore} 381 | 382 | \end{tikzpicture} 383 | 384 | \bigskip 385 | 386 | \begin{Note} 387 | A~\component{ResourceStore} will \emph{try} to satisfy 388 | any \component{RepresentationPreferences} passed to it, 389 | but only if this is reasonably easy for the store in question. 390 | For instance, a~SPARQL endpoint can typically 391 | generate N-Triples as easily as Turtle, 392 | so it makes sense to directly generate N-Triples 393 | if the client prefers this. 394 | On the other hand, 395 | a~file system will typically only have one representation on disk, 396 | so it is fine to always serve that single representation, 397 | regardless of client preferences. 398 | \columnbreak 399 | 400 | Optionally, a~\component{RepresentationConvertingStore} can be used 401 | to satisfy client preferences more accurately. 402 | It has access to \component{RepresentationConverter} instances, 403 | which could (for instance) convert a~stream of quads 404 | into Turtle or a~specific JSON-LD frame. 405 | It can decorate any existing \component{ResourceStore} 406 | to extend it with more kinds of representations 407 | such as different content types. 408 | \columnbreak 409 | 410 | A~\component{CompositeResourceStore} can be used 411 | to have multiple back-ends on one pod, 412 | each answering to different URL patterns. 413 | This mechanism \emph{could} be used 414 | also to serve large files like images, 415 | or static assets such as apps or scripts. 416 | \end{Note} 417 | 418 | \section*{ResourceStore atomicity and conditional requests} 419 | \begin{tikzpicture} 420 | \umlinterface{ResourceStore}{}{\ResourceStoreBody} 421 | 422 | \umlclass[below=1 of ResourceStore]{Conditions}{ 423 | + matchesEtag : string[]\\ 424 | + notMatchesEtag : string[]\\ 425 | + modifiedSince: date?\\ 426 | + unmodifiedSince: date?\\ 427 | }{ 428 | + matches(metadata : RepresentationMetadata): boolean\\ 429 | + matches(eTag : string?, lastModified : date?): boolean\\ 430 | } 431 | \umldep[arg=uses,pos=0.5]{ResourceStore}{Conditions} 432 | 433 | \umlinterface[right=2 of ResourceStore.north east, anchor=south west]{AtomicResourceStore}{}{ 434 | } 435 | \umlimpl{AtomicResourceStore}{ResourceStore} 436 | 437 | \umlclass[right=2 of ResourceStore.east]{LockingResourceStore}{ 438 | - ResourceStore source\\ 439 | - ResourceLocker locks\\ 440 | }{ 441 | } 442 | \umlimpl{LockingResourceStore}{ResourceStore} 443 | \umlimpl{LockingResourceStore}{AtomicResourceStore} 444 | \umlunicompo[mult=1,anchor1=202,anchor2=-6]{LockingResourceStore}{ResourceStore} 445 | 446 | \umlinterface[right=2 of LockingResourceStore.east]{ResourceLocker}{ 447 | }{ 448 | + acquire(ResourceIdentifier) : Promise\\ 449 | } 450 | \umluniaggreg[mult=1]{LockingResourceStore}{ResourceLocker} 451 | 452 | \umlinterface[below=1 of ResourceLocker.south]{Lock}{ 453 | }{ 454 | + release() : Promise\\ 455 | } 456 | \umldep[arg=creates,pos=0.5]{ResourceLocker}{Lock} 457 | 458 | \end{tikzpicture} 459 | 460 | \bigskip 461 | 462 | \begin{Note} 463 | The \component{ResourceStore} interface has been designed 464 | such that each of its methods \emph{can} be implemented 465 | in an atomic way: 466 | for each CRUD operation, 467 | only one dedicated method needs to be called. 468 | A~fifth method enables the optimization of partial updates 469 | with \verb!PATCH!. 470 | It is up to the implementer of the interface 471 | to (not) make an implementation atomic. 472 | For some implementations, 473 | such as triple stores or other database back-ends, 474 | atomicity is a~given. 475 | We~\emph{could} explicitly indicate atomicity 476 | by having such implementations 477 | implement the (otherwise empty) \component{AtomicResourceStore} interface 478 | as a~tag. 479 | 480 | \columnbreak 481 | 482 | Some back-ends are not atomic by themselves, 483 | such as a~file system, 484 | where a~read+append sequence could unknowingly 485 | be interrupted by a~write 486 | that thereby breaks atomicity. 487 | Instead of having to implement 488 | a~dedicated locking mechanism for every non-atomic back-end, 489 | these stores can be made atomic 490 | by decorating them with a~\component{LockingResourceStore}. 491 | This class wraps another \component{ResourceStore} 492 | and adds a~locking mechanism, 493 | of which different implementations can exist. 494 | 495 | \columnbreak 496 | 497 | It is important to emphasize 498 | that atomicity is \emph{not} the only reason 499 | for the design of the \component{ResourceStore} interface. 500 | Another consideration is \field{modifyRepresentation}, 501 | which allows us to optimize modifications in a~backend-specific way. 502 | Since we expect small modifications to larger resources 503 | to be a~common for Solid apps, 504 | we need to be able to handle those efficiently. 505 | \field{modifyRepresentation} gives implementations the freedom 506 | on how to apply patches, 507 | such that they can pick whichever option is most efficient 508 | for a~given patch 509 | and, if desired, support atomicity. 510 | 511 | \end{Note} 512 | 513 | \begin{Note} 514 | The \component{Conditions} class 515 | represents the conditions of an HTTP conditional request. 516 | It~is passed to all write methods 517 | (and possibly also read) of \component{ResourceStore}. 518 | The~store is responsible for validating conditions 519 | at the right moment 520 | and, should validation fail, 521 | for aborting the modification by throwing an error. 522 | 523 | \columnbreak 524 | If the store knows how to validate conditions, 525 | it can use the raw exposed fields on \component{Conditions}. 526 | If it does not, 527 | it can call \field{modifyRepresentation} 528 | with both ETag and the last modified date, 529 | or try one of them before the other. 530 | Finally, 531 | if it knows about neither ETag nor last modified date, 532 | it can pass the metadata as a~whole. 533 | 534 | \columnbreak 535 | The conditions argument is optional, 536 | and only passed for conditional requests. 537 | If a~store decides not to support conditional requests, 538 | it must throw an error if~conditions are passed. 539 | 540 | \end{Note} 541 | 542 | \clearpage 543 | 544 | \section*{Patch} 545 | \begin{Note} 546 | A~\component{Patch} contains a~description 547 | of changes to be made to a~certain 548 | (representation of a) resource. 549 | The \component{Patch} object itself 550 | does not know how to \emph{apply} this patch; 551 | it is merely a~data object. 552 | \columnbreak 553 | 554 | \null 555 | \columnbreak 556 | 557 | \null 558 | \end{Note} 559 | 560 | \bigskip 561 | 562 | \begin{tikzpicture} 563 | \umlinterface{Patch}{}{} 564 | 565 | \umlclass[right=2 of Patch.north east,anchor=south west]{LineBasedPatch}{ 566 | + deletions : Map\\ 567 | + additions : Map\\ 568 | }{} 569 | \umlimpl[anchors=west and north east]{LineBasedPatch}{Patch} 570 | 571 | \umlclass[below=1 of LineBasedPatch.south west,anchor=north west]{GraphPatternPatch}{ 572 | + where : Array\\ 573 | + delete : Array\\ 574 | + insert : Array\\ 575 | }{} 576 | \umlimpl[anchor2=east]{GraphPatternPatch}{Patch} 577 | 578 | \umlclass[below=1 of GraphPatternPatch.south west,anchor=north west]{BinaryPatch}{}{} 579 | \umlimpl[anchors=north west and south east]{BinaryPatch}{Patch} 580 | 581 | \umlclass[below=1 of BinaryPatch.south west,anchor=north west]{ImageFilter}{}{} 582 | \umlimpl[anchors=north west and south]{ImageFilter}{Patch} 583 | \end{tikzpicture} 584 | 585 | \begin{Note} 586 | A~\component{ResourceStore} \emph{might} have knowledge 587 | on how to apply certain types of patches itself. 588 | For instance, file-based stores 589 | might have built-in support for \component{LineBasedPatch}, 590 | and SPARQL endpoints or in-memory RDF stores 591 | likely have built-in support for \component{GraphPatternPatch}. 592 | \columnbreak 593 | 594 | There is case to be made for a~\component{Patcher} interface 595 | for objects that can apply all patches of a~certain type 596 | to certain representations. 597 | For instance, 598 | a~\component{GraphPatternPatch} could be applied 599 | to RDF graphs serialized as documents, 600 | by a~\component{GraphPatternPatcher} 601 | that operates independently of any specific store. 602 | 603 | \columnbreak 604 | 605 | \null 606 | \end{Note} 607 | 608 | \clearpage 609 | 610 | \section*{Quota} 611 | \begin{tikzpicture} 612 | \umlsimpleclass[]{AdministrationApi}{}{} 613 | \umlinterface[right=2 of AdministrationApi]{HttpHandler}{}{} 614 | \umlimpl{AdministrationApi}{HttpHandler} 615 | 616 | \umlinterface[below=2 of AdministrationApi]{SizeReporter}{}{ 617 | + getSize(ResourceIdentifier) : Promise\\ 618 | } 619 | \umldep[arg=uses,pos=0.5]{AdministrationApi}{SizeReporter} 620 | 621 | \umlclass[right=2 of SizeReporter]{SizeCache}{ 622 | - source : ResourceStore\\ 623 | - reporter : SizeReporter\\ 624 | }{} 625 | \umlimpl{SizeCache}{SizeReporter} 626 | \umlunicompo[mult=1,anchor1=202,anchor2=-12]{SizeCache}{SizeReporter} 627 | 628 | \umlinterface[below=1.5 of SizeCache]{ResourceStore}{}{} 629 | \umlimpl{SizeCache}{ResourceStore} 630 | \umlunicompo[mult=1,anchor1=-124,anchor2=128]{SizeCache}{ResourceStore} 631 | 632 | \umlsimpleclass[below=2 of SizeReporter]{FileSystemStore} 633 | \umlimpl{FileSystemStore}{ResourceStore} 634 | \umlimpl{FileSystemStore}{SizeReporter} 635 | 636 | \end{tikzpicture} 637 | 638 | \begin{Note} 639 | Storage quota can be retrieved 640 | through the \component{AdministrationApi}, 641 | which is an independent \component{HttpHandler} 642 | that accesses a~\component{SizeReporter}. 643 | The \component{SizeReporter} interface 644 | can be implemented by stores such as \component{FileSystemStore}. 645 | 646 | \columnbreak 647 | Since computing quota can be expensive, 648 | a~\component{SizeCache} 649 | could maintain quota for subpaths, 650 | which it invalidates upon write operations. 651 | 652 | \columnbreak 653 | \null 654 | 655 | \end{Note} 656 | 657 | \clearpage 658 | 659 | \section*{ACL caching} 660 | \begin{tikzpicture} 661 | \umlclass{AclBasedAuthorizer}{ 662 | - source : AclCache\\ 663 | }{ 664 | + ensurePermissions(Credentials, 665 | ResourceIdentifier,\\* 666 | PermissionSet) : Promise\\ 667 | } 668 | 669 | \umlclass[right=2 of AclBasedAuthorizer]{AclCache}{ 670 | - source : ResourceStore\\ 671 | }{} 672 | \umldep[arg=uses,pos=0.5]{AclBasedAuthorizer}{AclCache} 673 | 674 | \umlinterface[below=1.5 of AclCache]{ResourceStore}{}{} 675 | \umlimpl{AclCache}{ResourceStore} 676 | \umlunicompo[mult=1,anchor1=-130,anchor2=128]{AclCache}{ResourceStore} 677 | 678 | \end{tikzpicture} 679 | 680 | \begin{Note} 681 | Since ACLs will be used frequently, 682 | we need a~mechanism for caching them. 683 | Importantly, we need a~way to \emph{invalidate} the cache 684 | every time a~write operation happens 685 | to ACLs that can affect a~given document. 686 | 687 | \columnbreak 688 | To this end, 689 | the \component{AclCache} will wrap around a~\component{ResourceStore} 690 | and intercept all write requests, 691 | such that it can invalidate parts of its cache 692 | when writes to ACL~documents arrive. 693 | 694 | \columnbreak 695 | \null 696 | 697 | \end{Note} 698 | 699 | \end{document} 700 | -------------------------------------------------------------------------------- /store-atomicity.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{article} 2 | \usepackage[utf8]{inputenc} 3 | 4 | % Page setup 5 | \usepackage[a3paper,landscape,margin=2cm]{geometry} 6 | 7 | % Typography 8 | \usepackage[T1]{fontenc} 9 | \usepackage[scaled]{berasans} 10 | \usepackage[scaled]{beramono} 11 | \renewcommand*\familydefault{\sfdefault} 12 | \usepackage{microtype} 13 | \usepackage{fnpct} 14 | \parindent 0pt 15 | 16 | % TikZ 17 | \usepackage{./tikz-uml} 18 | \usetikzlibrary{positioning} 19 | 20 | % Headings 21 | \makeatletter 22 | \def\@maketitle{% 23 | {\LARGE\bf\@title\par} 24 | \vskip .5em 25 | {\large\@author\ -- \@date\par} 26 | \vskip 2em 27 | } 28 | \makeatother 29 | 30 | % Notes 31 | \usepackage{multicol} 32 | \newenvironment{Note} 33 | {\begin{multicols}{3}% 34 | \parskip 1em} 35 | {\end{multicols}} 36 | 37 | % Lists 38 | \usepackage{enumitem} 39 | \setlist{nosep,topsep=-10pt} 40 | 41 | % Document metadata 42 | \title{ 43 | Solid server -- Store atomicity 44 | \it (status: obsolete) 45 | } 46 | \author{Ruben Verborgh} 47 | \date{August 13, 2019} 48 | 49 | \begin{document} 50 | 51 | \maketitle 52 | 53 | \section*{ResourceStore and atomic operations} 54 | \begin{tikzpicture} 55 | \umlinterface{ResourceStore}{}{ 56 | + getRepresentation(ResourceIdentifier, RepresentationPreferences) : Promise\\ 57 | + addResource(container : ResourceIdentifier, Representation) : Promise\\ 58 | + setRepresentation(ResourceIdentifier, Representation) : Promise\\ 59 | + deleteResource(ResourceIdentifier) : Promise\\ 60 | + modifyResource(ResourceIdentifier, Patch) : Promise\\ 61 | } 62 | 63 | \umlinterface[right=2 of ResourceStore.north east, anchor=south west]{AtomicResourceStore}{}{ 64 | } 65 | \umlimpl{AtomicResourceStore}{ResourceStore} 66 | 67 | \umlclass[right=2 of ResourceStore.east]{LockingResourceStore}{ 68 | - ResourceStore source\\ 69 | - ResourceLocker locks\\ 70 | }{ 71 | } 72 | \umlimpl{LockingResourceStore}{ResourceStore} 73 | \umlimpl{LockingResourceStore}{AtomicResourceStore} 74 | \umlunicompo[mult=1,anchor1=197,anchor2=-5]{LockingResourceStore}{ResourceStore} 75 | 76 | \umlinterface[right=2 of LockingResourceStore.east]{ResourceLocker}{ 77 | }{ 78 | + acquire(ResourceIdentifier) : Promise\\ 79 | } 80 | \umluniaggreg[mult=1]{LockingResourceStore}{ResourceLocker} 81 | 82 | \umlinterface[below=1 of ResourceLocker.south]{Lock}{ 83 | }{ 84 | + release() : Promise\\ 85 | } 86 | \umldep[arg=creates,pos=0.5]{ResourceLocker}{Lock} 87 | 88 | \end{tikzpicture} 89 | 90 | \hyphenation{ResourceStore} 91 | 92 | \begin{Note} 93 | The \textbf{ResourceStore} interface has been designed 94 | such that each of its methods can be implemented 95 | in an \emph{atomic} way: 96 | for each CRUD operation% 97 | \footnote{% 98 | There are 5~operations rather than~4 99 | because we distinguish between 100 | full representations update for \texttt{PUT} 101 | and partial updates for \texttt{PATCH}. 102 | }, 103 | only one dedicated method needs to be called. 104 | It is up to the implementer of the interface 105 | to (not) make an implementation atomic. 106 | For some implementations, 107 | such as triple stores or other database back-ends, 108 | atomicity is a~given. 109 | We~\emph{could} explicitly indicate atomicity 110 | by having such implementations 111 | implement the (otherwise empty) \textbf{AtomicResourceStore} interface 112 | as a~tag. 113 | 114 | \columnbreak 115 | 116 | Some implementations are \emph{not} atomic by default, 117 | such as a~file system, 118 | where a~read+append sequence could unknowingly 119 | be interrupted by a~write 120 | that thereby breaks atomicity. 121 | Such non-atomic stores could be made atomic 122 | by decorating them with a~\textbf{LockingResourceStore}. 123 | This class wraps another \textbf{ResourceStore} 124 | with a~locking mechanism, 125 | which can be implemented in different ways. 126 | An example method implementation is listed on the right. 127 | 128 | \columnbreak 129 | 130 | \begin{verbatim} 131 | async function modifyResource(id, patch) { 132 | const lock = await this._locks.acquire(id); 133 | try { return await this._source.modifyResource(id, patch); } 134 | finally { await lock.release(); } 135 | } 136 | \end{verbatim} 137 | 138 | \end{Note} 139 | 140 | \subsection*{Design considerations} 141 | 142 | \begin{Note} 143 | It is important to emphasize 144 | that atomicity is \emph{not} the only reason 145 | for the design of the \textbf{ResourceStore} interface. 146 | The other consideration is in the 5\textsuperscript{th} method 147 | \verb!modifyResource!, 148 | which allows us to optimize modifications in a~backend-specific way. 149 | Since we expect small modifications to larger resources 150 | to be a~common pattern for Solid apps, 151 | we need to be able to handle those efficiently. 152 | 153 | \columnbreak 154 | 155 | A~simpler implementation with 4~methods 156 | could support \verb!PATCH! as follows: 157 | \begin{enumerate} 158 | \item call \verb!getRepresentation! 159 | \item apply the patch 160 | \item call \verb!setRepresentation! 161 | \end{enumerate} 162 | 163 | However, in addition to violating atomicity 164 | (or requiring another locking mechanism), 165 | it would also give suboptimal results 166 | when the resource is large 167 | and the patch is just a~single triple. 168 | Moreover, it would be unnecessarily complex and slow 169 | for the case of triple stores, 170 | which support patches natively. 171 | 172 | \columnbreak 173 | In contrast, 174 | \verb!modifyResource! gives implementations the freedom 175 | on how to apply patches, 176 | such that they can pick whichever option is most efficient 177 | for a~given patch 178 | and, if desired, support atomicity. 179 | 180 | \end{Note} 181 | 182 | \section*{ResourceStore and conditional requests} 183 | \begin{Note} 184 | With the above, 185 | we have established that \textbf{ResourceStore}: 186 | \begin{itemize} 187 | \item supports all CRUD requests; 188 | \item can support all types of patches efficiently; 189 | \item support atomicity 190 | (regardless of native support by the back-end). 191 | \end{itemize} 192 | 193 | \columnbreak 194 | However, the proposed mechanism 195 | does \emph{not} support conditional HTTP requests (RFC~7232), 196 | which must be aborted if the resource 197 | prior to modification 198 | does not satisfy certain conditions. 199 | These are not supported because: 200 | \begin{itemize} 201 | \item \textbf{ResourceStore} cannot abort, 202 | because it does not know the conditions. 203 | \item Callers of \textbf{ResourceStore} know the conditions, 204 | but they cannot check them in an atomic way, 205 | since they would not be able to prevent modifications 206 | in between the \verb!getRepresentation! call for checking the conditions, 207 | and the subsequent modification call. 208 | \end{itemize} 209 | 210 | \columnbreak 211 | Hence, 212 | we explore three different extensions to the architecture 213 | that aim to support conditional requests, 214 | and analyze their properties. 215 | 216 | \end{Note} 217 | 218 | \clearpage 219 | 220 | \section*{Approach 1 to conditional requests: \emph{transactions}} 221 | \begin{tikzpicture} 222 | \umlinterface{ResourceStore}{}{ 223 | + createTransaction(ResourceIdentifier) : Promise\\ 224 | } 225 | 226 | \umlinterface[right=2 of ResourceStore]{ResourceTransaction}{}{ 227 | + getRepresentation(RepresentationPreferences) : Promise\\ 228 | + addResource(Representation) : Promise\\ 229 | + setRepresentation(Representation) : Promise\\ 230 | + deleteResource() : Promise\\ 231 | + modifyResource(Patch) : Promise\\ 232 | + release() : Promise\\ 233 | } 234 | \umldep[arg=creates,pos=0.5]{ResourceStore}{ResourceTransaction} 235 | \end{tikzpicture} 236 | 237 | \subsection*{Description} 238 | 239 | \begin{Note} 240 | The original \textbf{ResourceStore} methods 241 | are moved into a~\textbf{ResourceTransaction} interface, 242 | which gets an additional \verb!release! method 243 | to end the transaction. 244 | The caller becomes responsible for steering the atomicity 245 | (but the implementation remains with the \textbf{ResourceStore}). 246 | 247 | \columnbreak 248 | Implementations do not need to be 249 | (and likely would not be) 250 | \emph{actual} transactions, 251 | in the sense that operations do not \emph{need} 252 | to be buffered until the very end when \verb!release! is called. 253 | They rather can function as \emph{locks/semaphores} 254 | that guarantee no other operations 255 | can happen in the meantime. 256 | 257 | \columnbreak 258 | \null 259 | 260 | \end{Note} 261 | 262 | \subsection*{Analysis} 263 | 264 | \begin{Note} 265 | Additional knowledge required by existing components: 266 | \begin{itemize} 267 | \item The caller knows how to validate request conditions. 268 | 269 | \item Every \textbf{ResourceStore} implementation must be transaction-aware 270 | (or at least \emph{lock}-aware), 271 | which is not the case for back-ends such as files. 272 | 273 | \item Callers of \textbf{ResourceStore} must be transaction-aware. 274 | \end{itemize} 275 | 276 | \bigskip 277 | When a~conditional request arrives, implementers must: 278 | \begin{enumerate} 279 | \item call \verb!createTransaction! 280 | \item call \verb!getRepresentation! 281 | \item check the conditions 282 | \item if the conditions are satisfied, call the modification method 283 | \item call \verb!release! 284 | \end{enumerate} 285 | 286 | \bigskip 287 | This comes with a~couple of caveats: 288 | \begin{itemize} 289 | \item We probably do not want to retrieve the full representation, 290 | but only the metadata (lazy loading can do that). 291 | 292 | \item It might result in the representation (or its metadata) 293 | being loaded twice: 294 | once by the caller when getting the representation, 295 | and once by the store internally when performing the modification. 296 | (The transaction can, however, cache this.) 297 | 298 | \item It assumes that \verb!getRepresentation! succeeds, 299 | which might not be the case for append-only stores. 300 | 301 | \item It assumes that \verb!createTransaction! is sufficiently cheap. 302 | 303 | \item It assumes that keeping a~lock/transaction open is sufficiently cheap. 304 | 305 | \item In general, 306 | it assumes that the caller has the best knowledge 307 | for checking the conditions in the cheapest way possible, 308 | which is not necessarily true. 309 | For instance, 310 | for one store it might be expensive to calculate ETag 311 | but not the last modified date, 312 | whereas it might be the opposite for another. 313 | A~certain store might even be able to determine 314 | that a~condition is met \emph{without} retrieving 315 | a~representation or its metadata 316 | (for instance, if its global last-modified date 317 | is not later than the requested one). 318 | \end{itemize} 319 | 320 | \columnbreak 321 | \null 322 | 323 | 324 | \columnbreak 325 | \null 326 | 327 | \end{Note} 328 | 329 | 330 | \section*{Approach 2 to conditional requests: \emph{passing a~validator}} 331 | \begin{tikzpicture} 332 | \umlinterface{ResourceStore}{}{ 333 | + getRepresentation(ResourceIdentifier, RepresentationPreferences, ConditionValidator) : Promise\\ 334 | + addResource(container : ResourceIdentifier, Representation, ConditionValidator) : Promise\\ 335 | + setRepresentation(ResourceIdentifier, Representation, ConditionValidator) : Promise\\ 336 | + deleteResource(ResourceIdentifier, ConditionValidator) : Promise\\ 337 | + modifyResource(ResourceIdentifier, Patch, ConditionValidator) : Promise\\ 338 | } 339 | 340 | \umlinterface[right=2 of ResourceStore]{ConditionValidator}{}{ 341 | + validate(RepresentationMetadata): boolean\\ 342 | } 343 | \umldep[arg=uses,pos=0.5]{ResourceStore}{ConditionValidator} 344 | \end{tikzpicture} 345 | 346 | \subsection*{Description} 347 | 348 | \begin{Note} 349 | A~\textbf{ConditionValidator} is passed to all write methods 350 | (and possibly also read) of \textbf{ResourceStore}. 351 | The store is responsible for calling \verb!validate! 352 | at the right moment, 353 | and for aborting the modification if validation fails. 354 | The caller is responsible for writing the validation code. 355 | 356 | \columnbreak 357 | (The validator argument \emph{could} be optional; 358 | if a~store decides to not support conditional requests, 359 | it must throw an error if a~validator is passed.) 360 | 361 | \columnbreak 362 | \null 363 | 364 | \end{Note} 365 | 366 | \subsection*{Analysis} 367 | 368 | \begin{Note} 369 | Additional knowledge required by existing components: 370 | \begin{itemize} 371 | \item The caller knows how to validate request conditions, 372 | given metadata. 373 | \end{itemize} 374 | 375 | \bigskip 376 | When a~conditional request arrives, implementers must: 377 | \begin{enumerate} 378 | \item call the modification method, 379 | passing in the validation code 380 | \item have \textbf{ResourceStore} call the validator 381 | at the right time 382 | \end{enumerate} 383 | 384 | \bigskip 385 | This comes with a~couple of caveats 386 | (details in previous section): 387 | \begin{itemize} 388 | \item It assumes that metadata is available. 389 | \item It assumes that retrieving metadata is sufficiently cheap. 390 | \item It assumes that the caller has the best knowledge for checking conditions. 391 | \item For every store implementation, 392 | it must be tested whether every method checks the conditions. 393 | \end{itemize} 394 | 395 | \columnbreak 396 | \null 397 | 398 | 399 | \columnbreak 400 | \null 401 | 402 | \end{Note} 403 | 404 | 405 | \clearpage 406 | \section*{Approach 3 to conditional requests: \emph{passing the conditions}} 407 | \begin{tikzpicture} 408 | \umlinterface{ResourceStore}{}{ 409 | + getRepresentation(ResourceIdentifier, RepresentationPreferences, Conditions) : Promise\\ 410 | + addResource(container : ResourceIdentifier, Representation, Conditions) : Promise\\ 411 | + setRepresentation(ResourceIdentifier, Representation, Conditions) : Promise\\ 412 | + deleteResource(ResourceIdentifier, Conditions) : Promise\\ 413 | + modifyResource(ResourceIdentifier, Patch, Conditions) : Promise\\ 414 | } 415 | 416 | \umlclass[right=2 of ResourceStore]{Conditions}{ 417 | + matchesEtag : string[]\\ 418 | + notMatchesEtag : string[]\\ 419 | + modifiedSince: date?\\ 420 | + unmodifiedSince: date?\\ 421 | }{ 422 | + matches(metadata : RepresentationMetadata): boolean\\ 423 | + matches(eTag : string?, lastModified : date?): boolean\\ 424 | } 425 | \umldep[arg=uses,pos=0.5]{ResourceStore}{Conditions} 426 | \end{tikzpicture} 427 | 428 | \subsection*{Description} 429 | 430 | \begin{Note} 431 | The \textbf{Conditions} themselves 432 | are passed to all write methods 433 | (and possibly also read) of \textbf{ResourceStore}. 434 | The store is responsible for validating conditions 435 | at the right moment, 436 | and for aborting the modification if validation fails. 437 | 438 | \columnbreak 439 | If the store knows how to validate conditions, 440 | it can use the raw exposed fields on \textbf{Conditions}. 441 | If it does not, 442 | it can call \verb!matches! 443 | with both ETag and the last modified date, 444 | or try one of them before the other. 445 | Finally, 446 | if it knows about neither ETag nor last modified date, 447 | it can simply pass the metadata as a~whole. 448 | 449 | \columnbreak 450 | (The conditions argument \emph{could} be optional; 451 | if a~store decides to not support conditional requests, 452 | it must throw an error if conditions are passed.) 453 | 454 | \end{Note} 455 | 456 | \subsection*{Analysis} 457 | 458 | \begin{Note} 459 | Additional knowledge required by existing components: 460 | \\*\emph{(none)} 461 | 462 | When a~conditional request arrives, implementers must: 463 | \begin{enumerate} 464 | \item call the modification method, 465 | passing in the validation code 466 | \item have \textbf{ResourceStore} check the conditions 467 | at the right time 468 | \end{enumerate} 469 | 470 | \bigskip 471 | This comes with a~couple of caveats: 472 | \begin{itemize} 473 | \item It assumes that the conditions do not change often. 474 | \item For every store implementation, 475 | it must be tested whether every method checks the conditions. 476 | \end{itemize} 477 | 478 | \columnbreak 479 | \null 480 | 481 | 482 | \columnbreak 483 | \null 484 | 485 | \end{Note} 486 | 487 | \end{document} 488 | --------------------------------------------------------------------------------