读者容错模式是当从一个数据模型中读取数据的时候,无论这个数据模型如何变换,尽最大可能读取自己需要的数据,达到读取数据健壮性的要求。例如我们有一个数据模型类:RainbowFish,表示一种鱼。有一个读写数据的类:RainbowFishSerializer,来获取RainbowFish的name、age、lengthMeters、weightTons等数据。但是RainbowFish是第三方提供,可能在迭代升级的时候改变了类结构,例如增加了子类:RainbowFishV2,记录了更加详细的信息。数据结构发生了变化,我们在读取的时候就要进行适配,避免由于改变导致的crash。
代码:
/**
*
* RainbowFish is the initial schema
*
*/
public class RainbowFish implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private int lengthMeters;
private int weightTons;
/**
* Constructor
*/
public RainbowFish(String name, int age, int lengthMeters, int weightTons) {
this.name = name;
this.age = age;
this.lengthMeters = lengthMeters;
this.weightTons = weightTons;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getLengthMeters() {
return lengthMeters;
}
public int getWeightTons() {
return weightTons;
}
}
/**
*
* RainbowFishV2 is the evolved schema
*
*/
public class RainbowFishV2 extends RainbowFish {
private static final long serialVersionUID = 1L;
private boolean sleeping;
private boolean hungry;
private boolean angry;
public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) {
super(name, age, lengthMeters, weightTons);
}
/**
* Constructor
*/
public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping,
boolean hungry, boolean angry) {
this(name, age, lengthMeters, weightTons);
this.sleeping = sleeping;
this.hungry = hungry;
this.angry = angry;
}
public boolean getSleeping() {
return sleeping;
}
public boolean getHungry() {
return hungry;
}
public boolean getAngry() {
return angry;
}
}
/**
*
* RainbowFishSerializer provides methods for reading and writing {@link RainbowFish} objects to
* file. Tolerant Reader pattern is implemented here by serializing maps instead of
* {@link RainbowFish} objects. This way the reader does not break even though new properties are
* added to the schema.
*
*/
public final class RainbowFishSerializer {
private RainbowFishSerializer() {
}
/**
* Write V1 RainbowFish to file
*/
public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException {
Map<String, String> map = new HashMap<>();
map.put("name", rainbowFish.getName());
map.put("age", String.format("%d", rainbowFish.getAge()));
map.put("lengthMeters", String.format("%d", rainbowFish.getLengthMeters()));
map.put("weightTons", String.format("%d", rainbowFish.getWeightTons()));
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(fileOut)) {
objOut.writeObject(map);
}
}
/**
* Write V2 RainbowFish to file
*/
public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException {
Map<String, String> map = new HashMap<>();
map.put("name", rainbowFish.getName());
map.put("age", String.format("%d", rainbowFish.getAge()));
map.put("lengthMeters", String.format("%d", rainbowFish.getLengthMeters()));
map.put("weightTons", String.format("%d", rainbowFish.getWeightTons()));
map.put("angry", Boolean.toString(rainbowFish.getAngry()));
map.put("hungry", Boolean.toString(rainbowFish.getHungry()));
map.put("sleeping", Boolean.toString(rainbowFish.getSleeping()));
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(fileOut)) {
objOut.writeObject(map);
}
}
/**
* Read V1 RainbowFish from file
*/
public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException {
Map<String, String> map = null;
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream objIn = new ObjectInputStream(fileIn)) {
map = (Map<String, String>) objIn.readObject();
}
return new RainbowFish(map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map.get("lengthMeters")),
Integer.parseInt(map.get("weightTons")));
}
}
/**
*
* Tolerant Reader is an integration pattern that helps creating robust communication systems. The
* idea is to be as tolerant as possible when reading data from another service. This way, when the
* communication schema changes, the readers must not break.
* <p>
* In this example we use Java serialization to write representations of {@link RainbowFish} objects
* to file. {@link RainbowFish} is the initial version which we can easily read and write using
* {@link RainbowFishSerializer} methods. {@link RainbowFish} then evolves to {@link RainbowFishV2}
* and we again write it to file with a method designed to do just that. However, the reader client
* does not know about the new format and still reads with the method designed for V1 schema.
* Fortunately the reading method has been designed with the Tolerant Reader pattern and does not
* break even though {@link RainbowFishV2} has new fields that are serialized.
*
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
// Write V1
RainbowFish fishV1 = new RainbowFish("Zed", 10, 11, 12);
LOGGER.info("fishV1 name={} age={} length={} weight={}", fishV1.getName(),
fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons());
RainbowFishSerializer.writeV1(fishV1, "fish1.out");
// Read V1
RainbowFish deserializedFishV1 = RainbowFishSerializer.readV1("fish1.out");
LOGGER.info("deserializedFishV1 name={} age={} length={} weight={}",
deserializedFishV1.getName(), deserializedFishV1.getAge(),
deserializedFishV1.getLengthMeters(), deserializedFishV1.getWeightTons());
// Write V2
RainbowFishV2 fishV2 = new RainbowFishV2("Scar", 5, 12, 15, true, true, true);
LOGGER.info(
"fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}",
fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(),
fishV2.getHungry(), fishV2.getAngry(), fishV2.getSleeping());
RainbowFishSerializer.writeV2(fishV2, "fish2.out");
// Read V2 with V1 method
RainbowFish deserializedFishV2 = RainbowFishSerializer.readV1("fish2.out");
LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}",
deserializedFishV2.getName(), deserializedFishV2.getAge(),
deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons());
}
}