Skip to content

Split configuration options by feature - #732

Merged
joachimvh merged 4 commits into
mainfrom
feat/config
May 19, 2021
Merged

joachimvh merged 4 commits into
mainfrom
feat/config

Conversation

@joachimvh

@joachimvh joachimvh commented May 10, 2021

Copy link
Copy Markdown
Member

Would advise not just going through the git diff for this.

Related to #261. Closes #295 (imo).

I have completely refactored how the configuration presets are stored in an attempt to make it more clear what is happening and make it easier to create new configurations, based on my experiences and what I heard from others using CSS.

I started out by creating a diagram of how everything is connected in our current architecture (turns out it's already pretty big) and marking which parts should be easy to change. This corresponds to a list of (mostly) orthogonal features that I would want to easily enable/disable or modify when creating a new config.

How I then implemented this is by making a folder for every feature. In each of those folders there are then multiple config that set the values that are specifically needed for that feature. E.g., in the authorization folder there is an acl.json that uses acl authorization and an allow-everything.json that disables authorization.

The idea is then that if a new user wants to set up CSS with specific features they just have to import 1 file from every folder and they have a completely valid CSS config with all the specifics they want. It's also set up in such a way that it is easy to add values in certain cases (e.g. if you just want to add a new converter to the conversion stack).

I added a a Configuration-format.md with some more documentation on how it all works. This is also why I recommend not (just) looking at this in the git diff, looking at it in a file explorer gives a better overview.

Some minor changes were also made to streamline certain parts of the config:

  • PatchHandlers now take a target store in the handle function. This makes it so much easier to just chain stores together. I also used this opportunity to clean up some things in our patch handler (it now tries to convert back to the original content type and keeps metadata).
  • MetadataExtractor is replaced by a ParallelHandler. Makes it more consistent with the MetadataWriter.
  • Some cases where the order of the handlers only partially matters have been updated:
    • The main initializer now consists of 3 parts that are executed in order: LoggerInitializer -> other initializers -> ServerInitializer. This makes it easier to append new initializers without having to worry about ruining the order.
    • The middleware handler, to make sure the CORS handler is always last (since that one can close the connection).

This is mostly finished, there are still a few things but this way a discussion can already be started since this is a subjective solution of course.

TODO:

  • Still have to change the IDP related configs, but this is mostly an independent block where no choices have to be made (besides the e-mail settings).
  • The new documentation would have to be linked from somewhere. We already have several documentation resources talking about Components.js so it might get a bit difficult right now to know where to look.
  • Make sure I tested all sub-configs.

@joachimvh
joachimvh marked this pull request as draft May 10, 2021 14:08
@joachimvh
joachimvh force-pushed the feat/config branch 4 times, most recently from c960487 to 661e20b Compare May 10, 2021 14:45
@RubenVerborgh

Copy link
Copy Markdown
Member

Would advise not just going through the git diff for this.

Woah!

I started out by creating a diagram of how everything is connected in our current architecture

Anything you can share?

E.g., in the authorization folder there is an acl.json that uses acl authorization and an allow-everything.json that disables authorization.

Excellent plan.

* of a {@link TargetExtractor}, {@link PreferenceParser}, {@link MetadataParser}, and {@link BodyParser}.
*/
export class BasicRequestParser extends RequestParser {
private readonly targetExtractor!: TargetExtractor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TargetParser then too, I suppose?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this was more a result of removing the class MetadataExtractor and using its input classes directly (which were MedataParsers), that's not a bad idea.

@RubenVerborgh

Copy link
Copy Markdown
Member
* `PatchHandlers` now take a target store in the `handle` function. This makes it so much easier to just chain stores together.

Can you explain this one a bit more?

I'm sure there was a reason for not passing the store around.

* (it now tries to convert back to the original content type and keeps metadata).

That worries me a bit. The general principle is good; just wonder if we need this in every patcher, or on a higher level.

@joachimvh

Copy link
Copy Markdown
Member Author

Anything you can share?

I first tried making a nice diagram but it cost me too much time to make it look useful so I just wrote it as nested blocks in a txt file, so it's not something I would put in the documentation 😅 : https://gist.github.com/joachimvh/c02534481045307e398bd044c4923fe0

Can you explain this one a bit more?
I'm sure there was a reason for not passing the store around.

The main reason I think we had a different store for the patch handler was because it generated internal/quads so we had an extra conversion store in there that always convert to text/turtle. I now changed how the handler works in that it converts back to the original content-type (so no longer forced to turtle) before writing back to the store. This way it no longer needs a specific conversion store there. If there was another reason I don't remember that.

The main reason I want to do this is because it makes our store stack a lot nice and easier to configure. Now you can just configure it like store A -> store B -> etc. Before you would have to branch of when you reached the patch handler since that one needed its own store.

That worries me a bit. The general principle is good; just wonder if we need this in every patcher, or on a higher level.

Potentially. It's hard to say until we have a second patcher. Perhaps some of these features could then be moved to a superclass.

Comment thread src/storage/patch/PatchHandler.ts Outdated
Comment thread src/storage/patch/SparqlUpdatePatchHandler.ts Outdated
Comment thread src/storage/patch/SparqlUpdatePatchHandler.ts Outdated
Comment thread src/storage/patch/PatchHandler.ts Outdated
Comment thread src/storage/patch/SparqlUpdatePatchHandler.ts
@RubenVerborgh

Copy link
Copy Markdown
Member

I now changed how the handler works in that it converts back to the original content-type (so no longer forced to turtle) before writing back to the store.

I take it SparqlUpdatePatchHandler is currently our only patcher?

I do like the change, but what I dislike is the conversion logic being part of the patcher.

It seems to me that there can be a whole class of patchers that:

  1. get a representation from the source
  2. convert it
  3. patch
  4. convert back
  5. write to the store
    and that only 3 is specific.

Can we perhaps just move 1/2/4/5 to a base class? In a different PR is okay.

@RubenVerborgh

RubenVerborgh commented May 12, 2021

Copy link
Copy Markdown
Member

I think the config/presets folder is a misnomer. The config folder are the presets.

Perhaps:

  • config/components
  • config/subsystems

or just even drop the presets folder altogether… but then templates would be lost. Unless we rename that one into _templates. But actually, those templates are all storage-*, so perhaps those 3 should go into .../storage/templates? Or maybe the templates can go and just be part of .../storage.

Do we still need thee config-* prefix?

@RubenVerborgh RubenVerborgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really excellent work! Much simpler than I thought it could be. Great!

I did not go through everything in detail, since I can just add suggestions in new PRs. Some suggestions already below.

In the comments, try to give a broader context, and describe from the noun rather than "this configuration". I have added some examples.

  • In the authentication folder:
    • Let's name them debug-test-agent, debug-auth-header, dpop-bearer.
  • authorization
    • acl.json => webacl.json
  • identity-email => identity/email?
  • Seems all middleware is just header setup? Should that be named headers?
    • header.json => constant-headers.json
    • websocket.json => updates-via.json

I wonder if we want to add a higher-level grouping above. Such as init, http, idp, ldp, and then have a lot in the latter two folders?

Comment thread config/config-default.json Outdated
Comment thread config/config-dynamic.json Outdated
Comment thread config/config-file.json Outdated
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"@graph": [
{
"comment": "This extractor always sets the credentials to the fixed value.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"comment": "This extractor always sets the credentials to the fixed value.",
"warning": "DO NOT USE IN PRODUCTION, ONLY FOR DEVELOPMENT, TESTING, OR DEBUGGING."
"comment": "This extractor always sets the credentials to the fixed value.",

It not allowed, then we can prepend this to the comment.

Should be added to all classes in this situation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not allowed since comment is a special predicate known by components.js. Would actually be interesting to have components.js support warning and have it emit warnings when such a file gets imported.

Comment thread config/presets/index/example.json Outdated
Comment thread config/presets/init/init.json Outdated
Comment thread config/presets/init/initializers/logger.json Outdated
@joachimvh

Copy link
Copy Markdown
Member Author

or just even drop the presets folder altogether… but then templates would be lost. Unless we rename that one into _templates. But actually, those templates are all storage-*, so perhaps those 3 should go into .../storage/templates? Or maybe the templates can go and just be part of .../storage.

I would not hide the templates inbetween the presets/components/subsystems as that would cause them to be harder to find, and they have a specific purpose. Probably should also rename them so they don't get confused with the storage component.

Originally I wanted to put them in the templates folder actually, but that would require us to have another import path in the package.json so components.js could find them. Could still go that route.

Do we still need the config-* prefix?

I don't think those were ever "needed", it just was the current way to name the config options.

In the comments, try to give a broader context, and describe from the noun rather than "this configuration". I have added some examples.

My problem was that you can't have line breaks in json strings, so I preferred to keep the descriptions short. More extensive descriptions could go into the accompanying markdown file.

Regarding that markdown file, I'm thinking now that it might be better to just have a README.md in every folder, instead of one big markdown file listing all the options. This would make it easier to make sure the documentation stays in sync with the actual available options.

  • Seems all middleware is just header setup? Should that be named headers?

Could do that. The CORS handler sort of breaks that though, since it will resolve OPTIONS requests itself instead of passing them on to the http handlers.

I wonder if we want to add a higher-level grouping above. Such as init, http, idp, ldp, and then have a lot in the latter two folders?

I was also thinking about something like that when making this. The one issue is consistency. I really like how in the current solution you just have to pick something from every folder. So if we did a higher-level grouping, everything would need to be 2 levels deep to be consistent. The main issue there is that several features are sort of independent, so there would be multiple high-level folders with only 1 entry (unless we make some very generic ones).

@RubenVerborgh

Copy link
Copy Markdown
Member

Okay with all of the above!

I really like how in the current solution you just have to pick something from every folder.

That is great indeed (and we should emphasize this more).

So if we did a higher-level grouping, everything would need to be 2 levels deep to be consistent.

Not necessarily though. We can keep the "pick one thing from every (leaf) folder", even if some are 1 level and others are 2.

The current breadth might be a bit intimidating.

@joachimvh

Copy link
Copy Markdown
Member Author

Not necessarily though. We can keep the "pick one thing from every (leaf) folder", even if some are 1 level and others are 2.

The current breadth might be a bit intimidating.

I agree it might be a bit intimidating, and it is quite certainly going to grow more in the future, so finding a good solution for this would be nice.

Always going to leaf folders is not going to work with the current solution though. Currently leaf folders are used for specific parts the user should not choose from. E.g., middleware/handlers contains all the current middleware handlers, but the user is not supposed to choose something there, only import a config from the middleware folder.

We could phrase it as "find the first folder that contains 1 or more config files and choose something there". This would work since those high level folders are not going to contain config files. I do feel like this woul drastically complicate the understanding of how those folders are supposed to be used though.

@RubenVerborgh

Copy link
Copy Markdown
Member

We could phrase it as "find the first folder that contains 1 or more config files and choose something there". This would work since those high level folders are not going to contain config files. I do feel like this woul drastically complicate the understanding of how those folders are supposed to be used though.

I think that one's okay. Pick something from every first level with files.

If needed, we can middleware/_handlers.

joachimvh added 2 commits May 17, 2021 11:37
This way the chain of ResourceStores is a bit easier to configure.
This commit also updates the SparqlUpdatePatchHandler to keep the metadata
and content-type of the resource that is being modified.
This way the MetadataExtractor class is not needed anymore
after a small change in the request parser.
@joachimvh

Copy link
Copy Markdown
Member Author

I had a look into how I could add high-level folders. Below a suggestion. The "from" statements are references to how that folder is currently named.

  • http
    • handler (from http)
    • middleware
    • server-factory
    • static
  • identity
    • email (from identity-email)
    • handler (from identity)
  • init
    • handler (from init)
  • ldp
    • authentication
    • authorization
    • handler (from ldp)
    • metadata-parser
    • metadata-writer
    • permissions
  • pod
    • handler (from pod)
  • storage
    • keyvalue (from storage)
    • resource-store
  • util
    • auxiliary
    • identifiers
    • index
    • logging
    • representation-conversion
    • resource-locker

This way everything is in a high-level folder instead of the separation suggested before but I think something like this looks fine? Other possibilities include having a conversion folder containing both index and representation-conversion.

pod only has 1 entry, but this might get merged with identity once we link up the IDP with pod provisioning.

I kept init on its own since that is the main entry point of the CSS. Could also be put in util or even http perhaps.

@RubenVerborgh

Copy link
Copy Markdown
Member

Excellent, looks good to me!

@joachimvh
joachimvh marked this pull request as ready for review May 18, 2021 13:09
@joachimvh

Copy link
Copy Markdown
Member Author

Last (?) changes: config templates are now in the templates folder, configs are split over high-level folders as discussed above, several configs have been renamed as suggested and also to have some more consistency.

One other big change is that I moved the documentation to the config folder. There is now a readme in the config folder and in each of the high-level folders. This way the config documentation can be seen by going to config folder on github, and the documentation of the options can be seen by going into the high-level folders.

The documentation could be more extensive, both in the markdown files as in the configs, but that would still cost quite a bit more time so I'm going to leave that as it currently is. This should already be a good starting point for people interested in the configs.

@joachimvh
joachimvh requested a review from RubenVerborgh May 18, 2021 13:13

@RubenVerborgh RubenVerborgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just perfect.

Comment thread config/README.md Outdated
Comment thread test/integration/config/server-dynamic-unsafe.json Outdated
@joachimvh
joachimvh merged commit 452032e into main May 19, 2021
@joachimvh
joachimvh deleted the feat/config branch May 19, 2021 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document config files

2 participants