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

Add EnvironmentLoader to customize how Migrations loads environment settings. #128

Closed
wants to merge 1 commit into from

Conversation

harawata
Copy link
Member

@harawata harawata commented Sep 4, 2018

There has been a few requests for alternative methods to configure environment settings (#90 , #122).

This PR adds a new interface EnvironmentLoader.
Migrations looks for its implementation via Java SPI at runtime (similarly to #107).

public interface EnvironmentLoader {
  Environment load(String environmentName, SelectedPaths paths);
}

@h3adache ,
I do not fully understand how you use Migrations, so please take a look and see if it meets the requirements (no rush, of course).
Here is the implementation that I used to test.

public class JarEnvironmentLoader extends PropertiesEnvironmentLoader {
  private static final Path ZIP_PATH = Paths.get("/.../environments.zip");

  @Override
  public Environment load(String environmentName, SelectedPaths paths) {
    String filename = "/environments/" + environmentName + ".properties";
    try (FileSystem fs = FileSystems.newFileSystem(ZIP_PATH, ClassLoader.getSystemClassLoader())) {
      Path path = fs.getPath(filename);
      try (InputStream is = Files.newInputStream(path)) {
        Properties properties = new Properties();
        properties.load(is);
        return buildEnvironment(properties);
      }
    } catch (IOException e) {
      throw new MigrationException("Environment file missing: " + filename + " in " + ZIP_PATH, e);
    }
  }
}

I'll add documentation if you approve the change.

@h3adache
Copy link
Member

h3adache commented Sep 4, 2018

Hi @harawata! Sorry I've been busy and meant to do the work for this myself. I was thinking something more simple on the lines of Resources or as described on ResolverUtil.
We use a similar concept on mybatis spring already (but using springs resource loaders). This allows people to load environments files that are bundled into their jar or somewhere else in their classpath.

This allows me to create and distribute a jar that can both up and down migrations for multiple environments.

I see the benefit of your approach is that you can load from any resources even a central config server so that's pretty neat in itself.

@harawata
Copy link
Member Author

harawata commented Sep 5, 2018

I see.
So, were you planning to introduce some special prefix like classpath: to specify resource path (e.g. --env=classpath:/environments/development.properties) ?

I see the benefit of your approach is that you can load from any resources even a central config server so that's pretty neat in itself.

It is flexible for sure, but it also feels like a little bit too over-engineered.

@h3adache
Copy link
Member

h3adache commented Jul 3, 2019

@harawata sorry for the late review.
Mostly just code consistency/style changes but the main comment is about being able to compose these environmentloaders.
It works well with what I had (I can refactor that to use this style) if we did it that way I think.

Order of checking would go Properties -> System (with prefix) -> Environment (with prefix) -> user custom that would seem to solve all our needs?

@h3adache
Copy link
Member

h3adache commented Jul 3, 2019

FYI I made the style/code consistency changes already if you want me to push those up to a shared branch? Just to save some time.

@harawata
Copy link
Member Author

harawata commented Jul 3, 2019

It works well with what I had (I can refactor that to use this style) if we did it that way I think.

I would be happy to take a look at your approach if it's simpler.
I am still not sure if this level of flexibility is necessary, to be honest.

if you want me to push those up to a shared branch? Just to save some time.

Sure, you can push directly to my branch, right?

@harawata
Copy link
Member Author

harawata commented Jul 3, 2019

Order of checking would go Properties -> System (with prefix) -> Environment (with prefix) -> user custom that would seem to solve all our needs?

I think so.

Copy link
Member

@h3adache h3adache left a comment

Choose a reason for hiding this comment

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

It works well with what I had (I can refactor that to use this style) if we did it that way I think.

I would be happy to take a look at your approach if it's simpler.
I am still not sure if this level of flexibility is necessary, to be honest.

I'll add the changes on top of yours.

if you want me to push those up to a shared branch? Just to save some time.

Sure, you can push directly to my branch, right?

I cant seem to push to your branch so I pushed to a same named branch on origin

environment = new Environment(existingEnvironmentFile());
EnvironmentLoader envLoader = null;
for (EnvironmentLoader found : ServiceLoader.load(EnvironmentLoader.class)) {
if (envLoader != null) {
Copy link
Member

Choose a reason for hiding this comment

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

We should allow multiple loaders using some default fallbacks

Copy link
Member Author

Choose a reason for hiding this comment

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

Any example use case of multiple loaders?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry I meant this to be from 213-222.
So removing the null check on 220 and adding their custom loader on top of set default ones (similar to spring properties resolver).
I am changing lines 220-223 to add my environment and system properties loaders.

@@ -43,7 +43,8 @@ public void execute(String... params) {
createDirectoryIfNecessary(paths.getDriverPath());

copyResourceTo("org/apache/ibatis/migration/template_README", Util.file(basePath, "README"));
copyResourceTo("org/apache/ibatis/migration/template_environment.properties", environmentFile());
copyResourceTo("org/apache/ibatis/migration/template_environment.properties",
new File(paths.getEnvPath(), options.getEnvironment() + ".properties"));
Copy link
Member

Choose a reason for hiding this comment

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

just a code consistency here. but envpath can be extracted.
also the rest use util.file (which can really just be changed to what you have here File(parent, file) or removed)

@@ -43,7 +43,8 @@ public void execute(String... params) {
createDirectoryIfNecessary(paths.getDriverPath());

copyResourceTo("org/apache/ibatis/migration/template_README", Util.file(basePath, "README"));
copyResourceTo("org/apache/ibatis/migration/template_environment.properties", environmentFile());
Copy link
Member

Choose a reason for hiding this comment

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

this method isn't used anymore. nor the supporting existingEnvironmentFile

hook_after_new
}

protected static final List<String> SETTING_KEYS;
Copy link
Member

Choose a reason for hiding this comment

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

private

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought that users might create a custom loader by subclassing this built-in one.

Copy link
Member

Choose a reason for hiding this comment

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

Ah that makes sense but I think if that's the case we should move this to something common.
This is the keys though, it is an implementation detail that we have changed before so depending on it might not be a good idea

protected Properties readProperties(String environmentName, SelectedPaths paths) {
File file = new File(paths.getEnvPath(), environmentName + ".properties");
FileInputStream inputStream = null;
try {
Copy link
Member

Choose a reason for hiding this comment

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

can be replaced with try with resources

Properties prop = new Properties();
prop.load(inputStream);
return prop;
} catch (FileNotFoundException e) {
Copy link
Member

Choose a reason for hiding this comment

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

Should this just be logged warning?

Copy link
Member Author

@harawata harawata Jul 5, 2019

Choose a reason for hiding this comment

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

If a user specified --env=someenv and there is no someenv.properties, throwing an exception seems appropriate.
What kind of use case are you thinking about?

Copy link
Member Author

Choose a reason for hiding this comment

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

So, you want Migrations to work even if there is no .properties file, am I right?

Copy link
Member

Choose a reason for hiding this comment

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

yup! I'm ok either way since there's a default one that's added during init.
So it's not a big deal. more for flexibility

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's a good idea.
We can, for example, ignore FileNotFoundException only when the environmentName is "development".

@h3adache
Copy link
Member

Can this be closed now @harawata?

@harawata harawata closed this Aug 20, 2019
@harawata
Copy link
Member Author

@h3adache ,
Yup! And thank you for sharing your wisdom on this topic!

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