OkaeriPoland / okaeri-configs

Simple Java/POJO config library written with love and Lombok
MIT License
83 stars 11 forks source link

System do migracji / wprowadzania zmian w konfiguracji #19

Closed P3ridot closed 2 years ago

P3ridot commented 3 years ago

Propozycja dodania adnotacji @Migrate

Działanie Adnotacja umożliwiłaby migracje pól np. ze starych systemów do zarządzania konfiguracją do nowych lub pozwoliłaby na zmianę lokalizacji pola. Jak by to miało wyglądać? Mamy przypadek, gdzie wartość trzymana jest pod kluczem np. _newkey więc plik jak jest ładowany to po nim szuka. Jeśli damy w takim przypadku do załadowania stary plik gdzie ta sama wartość była trzymana pod kluczem np. _oldkey to jej nie znajdzie. Adnotacja Migrate miałaby działać na takiej zasadzie, że jeśli przy ładowaniu nie znajdzie nigdzie wartości w configu pod nowym (aktualnym) kluczem to spróbuje ją znaleźć po starym kluczu z adnotacji Migrate. Dodatkowo jeśli znajdzie wartość pod starym kluczem to się pozbędzie się starego pola z pliku i przypisze jego wartość nowemu kluczowi.

config.yml (wersja 1):

old_key: value

config.yml (wersja 2):

new_key: value

Configuration.java

public class Configuration extends OkaeriConfig {

  @Migrate("old_key")
  @CustomKey("new_key")
  public String value = "Lorem ipsum";

}

Nie jestem pewien czy wszystko dobrze opisałem ale mam nadzieje, że tak.

dasavick commented 2 years ago

Po dłuższym przemyśleniu wydaje mi się, że ten sposób migrowania konfiguracji nie ma kompletnie sensu, bo rozwiązuje jedynie bardzo proste przypadki i przez to nie jest zbytnio użyteczny (jeśli w ogóle).

Podczas gdy jakaś drobna abstrakcja i wsparcie może by się przydało, pewnie trzeba by to zrealizować poprzez pliki migracji (jakiś interfejs do implementowania) i wtedy najwyżej nad root configiem albo niżej zależnie od potrzeb można by dać adnotację rejestrująca.

Prawda jest jednak taka, że to po prostu byłby glorified override OkaeriConfig#load posypany brokatem abstrakcji — migracje są obecnie już możliwe w ten sposób i są znacznie bardziej elastyczne.

Ewentualny system migracji jedynie mógłby standaryzować to w jakiś sposób. Zakładam jednak, że i tak pewnie niedużo więcej niż automatyczne odpalanie z adnotacji (bo nie każdy np. chce mieć pole version, może chcieć sprawdzać w inny sposób czy migracja jest applicable itp.) — czyli po prostu przeniesienie linijek odwołujących się do migracji z #load do adnotacji.

Przykład prostego rename

@Override
public OkaeriConfig load() throws OkaeriException {

    super.load();

    if (this.getConfigurer().keyExists("oldKey")) {
        this.set("newKey", this.getConfigurer().getValue("oldKey"));
    }

    return this;
}

Należy zwrócić uwagę, że podczas gdy większość backendów załaduje za pierwszym razem do pamięci nieistniejące już w POJO klucze, by design przy następnym zapisie zostają one pominięte.

Ze względu na to, że nie ma już gettera do starego klucza oraz np. załadowanie backendem json gdzie kiedyś był int, może zwrócić double albo float, należy:

Przykład promocji poziomu

@Override
public OkaeriConfig load() throws OkaeriException {

    super.load();

    Map<String, Object> document = this.asMap(this.getConfigurer(), true);
    Object mysqlPassword = this.extractValue(document, "mysql->password");
    if (mysqlPassword != null) {
        this.set("mysqlPassword", mysqlPassword);
    }

    return this;
}

protected Object extractValue(Map<?, ?> document, String path) {
    for (String part : path.split("->")) {
        Object element = document.get(part);
        if (element instanceof Map) {
            document = (Map<?, ?>) element;
            continue;
        }
        return element;
    }
    return null;
}

Funkcja extractValue jest zapożyczona z okaeri-persistence, to chyba najbardziej uniwersalny sposób na dobranie się do wartości głębiej, który powinien działać w niezależnie od backendu. Tutaj akurat prawdopodobnie przydałoby się coś bardziej ujednoliconego wbudowane w okaeri-configs. Analogicznie można zbudować przeniesienie głębiej (insertValue) lub skorzystać z setterów w nowym POJO.

dasavick commented 2 years ago

Dodane w 4.0.0-beta14. Przykłady użycia w https://github.com/FunnyGuilds/FunnyGuilds/commit/209249b2389298a88559a38713ef8331f7253aca.

public class T0001_Update_player_list_animated extends NamedMigration {

    public T0001_Update_player_list_animated() {
        super(
                "Preserve behavior of empty tablist pages by updating player-list-animated",
                when(
                        match("pages", v -> v instanceof Collection && ((Collection<?>) v).isEmpty()),
                        update("player-list-animated", (old) -> false)
                )
        );
    }
}