mercredi 29 mai 2019

Writing data classes with builder pattern

I have a data class which uses a builder to create the object and stores the data in a buffer in serialized form. I am planning to change the class to add and remove some fields. There are systems that will use both version of the class to create data i.e. the current version with all fields and newer version with removed/added fields. I am trying to see what is the best way to do this so that this is backward compatible(without breaking any consumer)?

I have couple of suggestions on how to do this but I am having a difficult time to pick one over the other.

Existing code

public class A implements Comparable<A>, Serializable {

private final Buffer buffer;
public static final Builder {
  private Header header//header with version
  private long creationTime;
  private SomeObject someObject;//this is removed in next version
  private OtherObject otherObject;//this is added in next version

  public Builder() { }

 //bunch of getters setters for fields

  public A build() {return new A(this);}

  private A(Builder b) {
   //build the object and put into the buffer
   validate()
  }
  private void validate() {//validates the object}

  public A(Buffer buf) {
   this.buffer=buf;
   validate();
  }
  public A(String encodedString) {
   this(ByteBuffer.wrap(encodedString));
  }
}
// consumers use this to get creationTime for object A
public long getCreationTime() {
 return buffer.getLong(OFFSET_CREATION_DATE);
}
}


Solution1: add new fields in the builder and use version in the header to decide which fields to use at build time (in build method) to create the object. Problem with this approach is that all the methods will exist at compile time to the consumers and unless they test their code every object will be valid. So it will be difficult to reason about which fields are required for which version at build time.

Solution2: Add a new builder in the class with the fields that you want. There will be duplicate fields that are in the existing builder. Consumers can then use the builder that they want. This seems to be cleaner because builders will be completely independent. Problem with this approach is that since we are adding and removing fields, fields will be at different offsets so the getters will have to change to use an offset based on versionType. This is also problematic for future versions because then we will have this gigantic class with lots of builders and logic in getters for every version

Solution3: Create a new class (let's say B) that extends A and have its own builder. This way the code is more modular. Problem is that now there will need to be some logic somewhere to differentiate and know which constructor to call. For example , if a consumer is passing base64 to get an object A, it will need to figure out which version it is.

String encodedString = "someString form of A"
A a = new A(encodedString);

Is there a recommended way to code these data classes with builder patterns to make it both future and backwards compatible.

Aucun commentaire:

Enregistrer un commentaire