Skip to content

Latest commit

 

History

History
184 lines (140 loc) · 5.08 KB

CustomizeTransitions.md

File metadata and controls

184 lines (140 loc) · 5.08 KB

Customize Transitions

You may start to create a solution based on single transitions as in the following example. This could be a backup application:

Transition<UUID> uuid = Start.to(UUID.class)
  .providedBy(UUID::randomUUID);

Transition<List<Path>> listOfFiles = Start.to(TypeInfo.listOf(TypeInfo.of(Path.class)))
  .initializedWith(Arrays.asList(Paths.get("a")));

Transition<BackupFolderName> backupFolderName = Derive.given(UUID.class)
  .state(BackupFolderName.class)
  .deriveBy(id -> BackupFolderName.of("backup-" + id.toString()));

Transition<BackupID> writeBackup = new WriteBackup();

Transitions transitions = Transitions.from(uuid, listOfFiles, backupFolderName, writeBackup);

try (TransitionWalker.ReachedState<BackupID> withBackupID = transitions.walker()
  .initState(StateID.of(BackupID.class))) {

  assertThat(withBackupID.current().backupFolderName().name())
    .startsWith("backup-");
}

You can avoid names if you just use many types to express the different results of your application. Most of the time these types are just wrapper:

@Value.Immutable
public interface BackupFolderName {
  @Value.Parameter
  String name();

  static BackupFolderName of(String name) {
    return ImmutableBackupFolderName.of(name);
  }
}

You can create your own implementation of an transition:

public final class WriteBackup implements Transition<BackupID> {

  @Override
  public StateID<BackupID> destination() {
    return StateID.of(BackupID.class);
  }

  public StateID<BackupFolderName> backupFolderNameStateID() {
    return StateID.of(BackupFolderName.class);
  }

  public StateID<List<Path>> listOfFilesStateID() {
    return StateID.of(TypeInfo.listOf(TypeInfo.of(Path.class)));
  }

  public Set<StateID<?>> sources() {
    return new HashSet<>(Arrays.asList(backupFolderNameStateID(), listOfFilesStateID()));
  }

  @Override
  public State<BackupID> result(StateLookup lookup) {
    BackupFolderName backupFolderName = lookup.of(backupFolderNameStateID());
    List<Path> listOfFiles = lookup.of(listOfFilesStateID());

    BackupID backupID = backupFiles(backupFolderName, listOfFiles);

    return State.of(backupID);
  }
  
  private static BackupID backupFiles(BackupFolderName backupFolderName, List<Path> listOfFiles) {
    // real backup not implemented ...
    
    return BackupID.of(backupFolderName, LocalDateTime.now());
  }
}

... which has BackupID as result:

@Value.Immutable
public interface BackupID {
  @Value.Parameter
  BackupFolderName backupFolderName();

  @Value.Parameter
  LocalDateTime timeStamp();

  static BackupID of(BackupFolderName backupFolderName, LocalDateTime timeStamp) {
    return ImmutableBackupID.of(backupFolderName, timeStamp);
  }
}

But if you want to customize this, you have to change the code which creates all these transitions. If you want to create a library any customization is very limited.

As your application consists of more or less independent parts it would be helpful if one can change only some parts and reuse most of the other parts. One way to do this: create a class whith all these parts:

@Value.Immutable
public class BackupApp {

  @Value.Default
  public Transition<UUID> uuid() {
    return Start.to(UUID.class).providedBy(UUID::randomUUID);
  }

  @Value.Default
  public Transition<List<Path>> listOfFiles() {
    return Start.to(TypeInfo.listOf(TypeInfo.of(Path.class)))
      .initializedWith(Arrays.asList(Paths.get("a")));
  }

  @Value.Default
  public Transition<BackupFolderName> backupFolderName() {
    return Derive.given(UUID.class)
      .state(BackupFolderName.class)
      .deriveBy(id -> BackupFolderName.of("backup-" + id.toString()));
  }

  @Value.Default
  public Transition<BackupID> writeBackup() {
    return new WriteBackup();
  }

  public BackupID startBackup() {
    try (TransitionWalker.ReachedState<BackupID> withBackupID = Transitions.from(uuid(), listOfFiles(), backupFolderName(), writeBackup())
      .walker()
      .initState(StateID.of(BackupID.class))) {
      return withBackupID.current();
    }
  }

  public static ImmutableBackupApp instance() {
    return ImmutableBackupApp.builder().build();
  }
}

This way you can just use it:

BackupID backupId = new BackupApp().startBackup();

assertThat(backupId.backupFolderName().name())
  .startsWith("backup-");

... or customize it with inheritance:

BackupID backupId = new BackupApp() {
  @Override public Transition<BackupFolderName> backupFolderName() {
    return Derive.given(UUID.class)
      .state(BackupFolderName.class)
      .deriveBy(id -> BackupFolderName.of("override-backup-" + id.toString()));
  }
}.startBackup();

assertThat(backupId.backupFolderName().name())
  .startsWith("override-backup-");

... or customize it using code generated by (immutables.org)[immutables.org]:

BackupID backupId = BackupApp.instance()
  .withBackupFolderName(Derive.given(UUID.class)
    .state(BackupFolderName.class)
    .deriveBy(id -> BackupFolderName.of("builder-backup-" + id.toString())))
  .startBackup();

assertThat(backupId.backupFolderName().name())
  .startsWith("builder-backup-");