Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracted url-utils package from Ghost core #114

Merged
merged 30 commits into from
Jun 13, 2019

Conversation

naz
Copy link
Contributor

@naz naz commented Jun 5, 2019

refs TryGhost/Ghost#10773

Adds @tryghost/url-utils module that is equivalent to current Ghost Url Service utils with exception to couple usage changes:

  1. The module has to be initialized through the init(params) method - this allowed to decouple it from the core's config dependency
  2. replacePermalink - now accepts additional parameter - timezone which allowed to remove the dependency on Settings Cache.
  3. Test cases were refactored to suit new initialization style and assertions were not changed

@rishabhgrg @kevinansfield would love to hear your opinions on these changes. Mainly wanted to double check on this additional initialization method and naming of the parameters it takes in.

To expand a bit on how initialization would work now. I'm thinking to have it initialized on server bootstrap right after the server configuration is set up. It gives one additional step but couldn't think of any nicer way of doing so.

TODO:

@rishabhgrg
Copy link
Contributor

@gargol Looking at all the ways we are using config in the file and the init method, I feel there are couple of ways this can re-structured -

  1. If the expectation to use util methods is to have initialization with config before using them, then probably its better to either wrap the methods as class/constructor or a factory/closure approach and expose that single method with config as parameter, as it explicitly forces developer to pass/initialize with valid config before using any of util methods uses config implicitly. The passed config can be checked once in init to be valid, and fail during initialization if not, allowing developer is free to use methods as pure fns worrying just about passing right params.
    Con : Methods which don't use config also go behind initialization, and can't be used directly with just params.

  2. While looking at methods use config, it seems each of them mostly use only one particular config, and not multiple. If we want to expose each util method individually, then its probably better to pass the config property being used as a param to the method, instead of having a config object for whole of utils. This allows each method to be exposed individually as pure fns, and also exposes method which don't use config to be used directly by developers.
    Con : Config needs to be fetched and passed as param wherever util method is used, instead of initializing it once( maybe at boot ) and then just using the utils as a service directly.

I feel either approach, though have their own cons, resolves the ambiguity of config being passed to util method. I am generally inclined towards second as a developer because of freedom to choose utils which don't have config, but looking from Ghost's perspective, its probably better to do first where utils is used as a service which is initialized once and then used everywhere. 😄

@naz
Copy link
Contributor Author

naz commented Jun 6, 2019

Awesome feedback, thanks! 💯 agree about the 2nd approach being the best.

My main concern was having to add config dependency in every place where functions are called in Ghost. For example for getSubdir method around 8 out 12 usages of this method would have to require a config dependency + it would have to be passed in as a parameter for each internal method that uses getSubdir (almost all internal utils methods). It would be a lot of shuffling around and adding dependencies to have pure functions in this util without having at least blogs url preconfigured.

This brings us to the 1st approach. Think it is the least intrusive approach and makes sense to have it hidden behind a constructor to have more explicit "you gotta initialize this thing before using". Will experiment with this approach 👍

@rishabhgrg
Copy link
Contributor

@gargol Yeah I agree that second approach is too cumbersome for our primary usecase, and while first can still be made flexible enough like (2) with slightly more work where even if you pass empty config, it allows you to use methods which don't require config, while throwing error for ones that do.

Just FYI that For (2), there is a non constructor/class as well if it suits better, where it takes advantage of JS closure to give us methods which has config initialized, without having do a proper url utils class setup. Either ways the instance returned will need to be preserved to use all around core. 😄
Roughly:

function UrlUtils(config) {

   return {
       method1: () => {},
       method2: () => {}
       ....
   }
}

export default UrlUtils;

@naz
Copy link
Contributor Author

naz commented Jun 6, 2019

@rishabhgrg @kevinansfield pushed out changes to the initialization of the module and have extracted a few methods into separate modules (the ones that were pure functions).

The rest, unfortunately, is very tied up to config.url value behaving like a "global" in some methods (specifically in getSubdir). Didn't figure out a nice way to abstract that away without running into circular dependency hell, would table it for future improvements? Or maybe you have some ideas on how to work around it? 🤔

@rishabhgrg
Copy link
Contributor

rishabhgrg commented Jun 7, 2019

@gargol I think a nice way to extract out methods to separate file as well as being able to use global config could be what we were thinking about doing with approach (2) here. All the methods can be put in separate file(s) taking config as an extra parameter, making them all pure methods, which allows them to be imported in other files as well as index.js. So if getSubdir, is pure method which takes config as extra param and returns its value, in our index getSubdir we can just wrap that method to pass it our global config, while in a method like deduplicateSubDir we can require getSubdir pure method and pass it config, which does not make a circular dependency.

The advantage with this approach is it adds all the benefits of (2) where util methods are pure methods ready to be exported when needed in future, while taking care of its main con where config needed to be passed everywhere in ghost

main concern was having to add config dependency in every place where functions are called in Ghost.

as it still has advantages of (1) where it can easily be initialized at once place with config. 🤔

In getSubdir file -

module.exports = function getSubdir(config) {
        // Parse local path location
        var localPath = url.parse(config.url).path,
        ...
        return subdir;
}

In deduplicateSubDir file -

const getSubdir = require('./getSubdir');
...
module.exports = function deduplicateSubDir(url, config) {
        var subDir = getSubdir(config),
        ...
       return url.replace(subDirRegex, '$1' + subDir + '/');
}

In index.js -

const getSubdir = require('./getSubdir');
const deduplicateSubDir = require('./deduplicateSubDir');
...
utils.getSubdir = () => getSubdir(config)
utils. deduplicateSubDir = (url) => deduplicateSubDir(url, config)
...

@naz
Copy link
Contributor Author

naz commented Jun 7, 2019 via email

@rishabhgrg
Copy link
Contributor

rishabhgrg commented Jun 7, 2019

@gargol Ah, I see what you mean! 🤔 Yeah that will mean we'll need to redefine joinUrl to take first parameter as config (we can skip doing specific config and just use config as whole) and rest as urls, probably something like joinUrl(config, ...rest). which technically make sense in context of ghost as joinUrl should know config.

tbh though I don't think we need to spend too much time to do this now, all of this can be done as part of cleanup later, we can just leave a comment and move on for now. Wdyt? 😄

@naz
Copy link
Contributor Author

naz commented Jun 7, 2019 via email

@rishabhgrg
Copy link
Contributor

Would merge this in and
integrate it with the core today. Wdyt?

@gargol Go for it 👍

@naz naz force-pushed the url-utils-from-ghost branch from 1a07e01 to c98ab69 Compare June 11, 2019 10:42
@naz naz changed the title url-utils package extraction from Ghost core Extracted url-utils package from Ghost core Jun 13, 2019
@naz naz merged commit 97e0bc5 into TryGhost:master Jun 13, 2019
naz added a commit to naz/Ghost-SDK that referenced this pull request Sep 24, 2020
naz added a commit to naz/Ghost-SDK that referenced this pull request Sep 28, 2020
naz added a commit to naz/Ghost-SDK that referenced this pull request Sep 28, 2020
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.

2 participants