· 5 years ago · Mar 03, 2021, 06:18 PM
1// ------------------------------
2// ----- Distance Provider ------
3// ------------------------------
4
5// Interface to Providers (Proxy to external third-party APIs)
6interface ISpatialProvider {
7 int getDistanceInMeters(LatLong from, LatLong to);
8 Date getLastRoadNetworkUpdate();
9}
10
11class CloudSpatialProvider implements ISpatialProvider {
12
13 public CloudSpatialProvider() { }
14
15 public int getDistanceInMeters(LatLong from, LatLong to) {
16 // We probably need to do a mapping between "our" LatLong class and the class
17 // used by the third-party library (in reality)
18 return com.cloudspatialprovider.api.FetchDistanceInMeters(from, to);
19 }
20
21 public Date getLastRoadNetworkUpdate() {
22 return com.cloudspatialprovider.api.lastRoadNetworkUpdate();
23 }
24
25}
26
27// ------------------------------
28// --------- Caching ------------
29// ------------------------------
30
31interface ICache {
32 String getEntry(String key);
33 boolean putEntry(String key, String value);
34}
35
36class LocalCache implements ICache {
37
38 private HashMap<String, Integer> cache;
39
40 public LocalCache() {
41 this.cache = new HashMap<>();
42 }
43
44 public String getEntry(String key) {
45 return cache.get(key);
46 }
47
48 public boolean putEntry(String key, String value) {
49 return cache.put(key, value);
50 }
51
52}
53
54// We can introduce other ICache implementations which e.g. use a co-located
55// cache / dedicated cache cluster etc.
56// Lets use a simple LocalCache for now
57
58// ------------------------------
59// ------ Distance Service ------
60// ------------------------------
61
62// Interface of our Distance Service
63interface IDistanceService {
64 int getDistanceInMeters(LatLong from, LatLong to);
65}
66
67class DistanceService implements IDistanceService {
68
69 // In reality we might want to inject a configuration object into our
70 // class to configure "constants" like the following one
71 private static final int NETWORK_UPDATE_CHECK_INTERVAL_SECONDS = 2 * 24 * 60 * 60; // 2 days
72
73 private ISpatialProvider spatialProvider;
74 private ICache cache;
75
76 // Using the Date API (which is kind of dangerous but I use it here for simplicity)
77 private Date lastNetworkUpdate;
78 private long lastNetworkUpdateCheckTimestamp; // In seconds
79
80 // Use dependency injection to inject the provider and cache
81 public DistanceService(ISpatialProvider spatialProvider, ICache cache) {
82 this.spatialProvider = spatialProvider;
83 this.cache = cache;
84 }
85
86 public DistanceService(ISpatialProvider spatialProvider) {
87 this(spatialProvider, null); // Caching disabled
88 }
89
90 public int getDistanceInMeters(LatLong from, LatLong to) {
91 // Update the last network update timestamp
92 // We also fetch the last network update information in case no cache is used
93 // since we might still want to have that information
94
95 // System.currentTimeMillis() is not monotonically increasing, so dangerous
96 // but I use it here for simplicity
97 long timeNow = System.currentTimeMillis() / 1000l; // In seconds
98
99 long timeSinceLastUpdate; // In seconds
100 if(this.lastNetworkUpdate == null) {
101 timeSinceLastUpdate = NETWORK_UPDATE_CHECK_INTERVAL_SECONDS
102 } else {
103 timeSinceLastUpdate = timeNow - this.lastNetworkUpdateCheckTimestamp;
104 }
105
106 if(timeSinceLastUpdate >= NETWORK_UPDATE_CHECK_INTERVAL_SECONDS) {
107 this.lastNetworkUpdateCheckTimestamp = timeNow;
108 this.lastNetworkUpdate = this.spatialProvider.getLastRoadNetworkUpdate();
109 }
110
111 // Cache is used
112 if(this.cache != null) {
113 return getCachingResult(from, to, this.lastNetworkUpdate);
114 }
115
116 // Caching is disabled
117 return this.spatialProvider.getDistanceInMeters(from, to, this.lastNetworkUpdate);
118 }
119
120 private int getCachingResult(LatLong from, LatLong to, Date validUntil) {
121 String cacheKey = from + "," + to; // toString() of LatLong class must be overwritten
122 String cacheEntry = this.cache.getEntry(cacheKey);
123
124 // The cache is now generic, so do the de/serialization of an entry here
125 // Pair<Integer, Date> holds the cache entrys distance in meters + last updated timestamp
126 Pair<Integer, Date> result = deserialize(cacheEntry); // deserialize cache entry
127
128 // if cache miss or the entry is too stale, request third-party API
129 if(result == null || result.getValue().before(validUntil)) {
130 // call third-party API
131 int distance = this.spatialProvider.getDistanceInMeters(from, to);
132 result = new Pair<>(distance, new Date());
133
134 // update cache
135 this.cache.putEntry(cacheKey, serialize(result));
136 }
137
138 return result.getKey();
139 }
140
141}