I have two URLs (urlA
and urlB
) and both the URL gives me a JSON response back in the same JSON format. Below is my JSON string which I am getting back by calling from urlA
. I have shorten it down by having only three reportRecords
for the understanding purpose. In general, it might have more than ~500 reportRecords
.
{
"aggRecords": {
"reportRecords": [
{
"min": 0,
"max": 12,
"avg": 0.3699187,
"count": 246,
"sumSq": 571,
"stddev": 1.4779372,
"median": 0,
"percentileMap": {
"95": 4
},
"metricName": "TransactionDuration",
"dimensions": {
"env": "dev",
"pool": "titan",
"Name": "PostProcessing",
"Type": "PostProcessing"
},
"value": 91
},
{
"min": 0,
"max": 23,
"avg": 2.3991289E-4,
"count": 1463031,
"sumSq": 3071,
"stddev": 0.045814946,
"median": 0,
"percentileMap": {
"95": 0
},
"metricName": "TransactionDuration",
"dimensions": {
"env": "dev",
"pool": "titan",
"Name": "ResourceContext",
"Type": "ResourceContext"
},
"value": 351
},
{
"min": 0,
"max": 1209,
"avg": 1.9203402,
"count": 7344636,
"sumSq": 71832774,
"stddev": 2.4683187,
"median": 2,
"percentileMap": {
"95": 4
},
"metricName": "TransactionDuration",
"dimensions": {
"env": "dev",
"pool": "titan",
"Name": "Client::Sync",
"Type": "Client::Sync"
},
"value": 14104200
}
]
}
}
Similarly I am also getting another JSON response back by calling urlB
and it is in the exact same JSON format as shown above but the values for all the keys are different.
Now what I need to do is, I need to serialize JSON response coming from both the URL's (urlA
and urlB
) in DataTransactionMetrics
object only and then iterate DataTransactionMetrics
object and extract relevant information from it in which I am interested in it. Meaning, I will be extracting few Name
values from the DataTransactionMetrics
object in which I am interested in it and populate it into hostMetricsList
object and at the end print out the full list.
Below is my full code which does the JSON serialization of both the URL's -
public class JSONParser {
private static RestTemplate restTemplate = new RestTemplate();
private static final Gson gson = new Gson();
private static final Type listOfMetricsType = new TypeToken<List<DataTransactionMetrics>>() {
}.getType();
private static final String BASE_URL = "http://data.host.com/reports/";
private static final String TRANSACTION_METRIC = "/Transaction?defaultDim=Name&defaultDim=Type&metric=TransactionDuration&percentile=95";
private static final String EVENT_METRIC = "/Event?defaultDim=Name&defaultDim=Type&metric=TotalCount&percentile=95";
private static final List<String> hostNames = Arrays.asList("hostA", "hostB", "hostC");
public static void main(String[] args) throws IOException {
List<HostMetrics> hostMetricsList = new ArrayList<HostMetrics>();
for (String hostName : hostNames) {
HostMetrics hostMetrics = new HostMetrics();
hostMetrics.sethostName(hostName);
Calendar startDate = new GregorianCalendar();
startDate.set(Calendar.MINUTE, 0);
Calendar endDate = (Calendar) startDate.clone();
startDate.set(Calendar.HOUR_OF_DAY, 0);
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm");
String startTime = df.format(startDate.getTime());
String endTime = df.format(endDate.getTime());
// construct urlA
String urlA = BASE_URL + hostName + TRANSACTION_METRIC + "&startTime=" + startTime
+ "&endTime=" + endTime;
// construct urlB
String urlB = BASE_URL + hostName + EVENT_METRIC + "&startTime=" + startTime
+ "&endTime=" + endTime;
// serialize everything in DataTransactionMetrics object, both the URL's
List<DataTransactionMetrics> dataMetrics = loadMetrics(urlA);
dataMetrics.addAll(loadMetrics(urlB));
// and then extract only few fields from it and populate in HostMetrics object
for (DataTransactionMetrics metrics : dataMetrics) {
if (metrics.getDimensions().getName().equalsIgnoreCase("DataSync")) {
hostMetrics.setTotalNumberOfSyncCall(metrics.getCount());
hostMetrics.setSyncNinetyFivePercentileCall(String.valueOf(metrics.getPercentileMap().get("15")));
hostMetrics.setSyncAvgCall(metrics.getAvg());
hostMetrics.setSyncMaxCall(metrics.getMax());
} else if (metrics.getDimensions().getName().equalsIgnoreCase("DataAsync")) {
hostMetrics.setTotalNumberOfAsyncCall(metrics.getCount());
hostMetrics.setAsyncNinetyFivePercentileCall(String.valueOf(metrics.getPercentileMap().get("15")));
hostMetrics.setAsyncAvgCall(metrics.getAvg());
}
}
hostMetricsList.add(hostMetrics);
}
// and then print out the list
System.out.println(hostMetricsList);
}
private static List<DataTransactionMetrics> loadMetrics(String url) {
String jsonString = restTemplate.getForObject(url, String.class);
JsonObject json = new JsonParser().parse(jsonString).getAsJsonObject();
JsonArray jarr = json.getAsJsonObject("aggRecords").getAsJsonArray("reportRecords");
return gson.fromJson(jarr, listOfMetricsType);
}
}
And below is my DataTransactionMetrics
class -
public class DataTransactionMetrics {
private String metricName;
private Map<String, Integer> percentileMap;
private String median;
private String stddev;
private String sumSq;
private String count;
private String avg;
private String max;
private String min;
// getters here
public Dimensions dimensions;
class Dimensions {
private String env;
private String pool;
@SerializedName("Name")
private String name;
// getters here
}
}
And below is my HostMetrics
class -
public class HostMetrics {
private String hostName;
private String totalNumberOfSyncCall;
private String syncNinetyFivePercentileCall;
private String syncAvgCall;
private String syncMaxCall;
private String totalNumberOfAsyncCall;
private String asyncNinetyFivePercentileCall;
private String asyncAvgCall;
// getters and setters here
}
Now I have got everything working in the above code. I am opting for code review to see whether we can improve anything here in the code.
-
\$\begingroup\$ Why don't you add Exception Handling? \$\endgroup\$TheCoffeeCup– TheCoffeeCup2014年08月18日 00:54:54 +00:00Commented Aug 18, 2014 at 0:54
-
\$\begingroup\$ @MannyMeng I already have added exception handling. Sorry I should have edited my question earlier. \$\endgroup\$arsenal– arsenal2014年08月18日 04:36:52 +00:00Commented Aug 18, 2014 at 4:36
2 Answers 2
Your constructor should create usable objects
Make sure that the constructor always creates a usable object. This saves your class's clients the trouble from debugging missing calls to init
or set
methods that are required.
Basically, if a method call is required before the class is usable, then that should be in the constructor.
Your HostMetrics
always has to have sethostName
called or it won't function as it appears to me. The correct way to do this would be to remove the sethostName
function and make the host name a constructor argument.
Extract methods
You could extract a constructMetricUrl
method in the HostMetrics
class to create the urls and get rid of the comments here:
// construct urlA
String urlA = BASE_URL + hostName + TRANSACTION_METRIC + "&startTime=" + startTime
+ "&endTime=" + endTime;
// construct urlB
String urlB = BASE_URL + hostName + EVENT_METRIC + "&startTime=" + startTime
+ "&endTime=" + endTime;
In general if you need a comment to say what something does, there is a good chance that that something should be a function call. The function name also serves as an automatic comment and improves readability.
Without having more information about the DataTransactionMetrics
and HostMetrics
classes I think that you should be able to move some of the functionality in your main()
method into member functions on these classes. A general rule of thumb is that if your function doesn't quite fit comfortably on your screen then the function is too long and should be broken up into sub-routines (or you're in dire need a of a bigger screen).
In essence with proper designed classes your main function would look something like this:
public static void main(String[] args) throws IOException {
List<HostMetrics> hostMetricsList = new ArrayList<HostMetrics>();
for (String hostName : hostNames) {
Calendar startDate = new GregorianCalendar();
startDate.set(Calendar.MINUTE, 0);
Calendar endDate = (Calendar) startDate.clone();
startDate.set(Calendar.HOUR_OF_DAY, 0);
HostMetrics hostMetrics = new HostMetrics(hostName, startDate, endDate);
hostMetricsList.add(hostMetrics);
}
// and then print out the list
System.out.println(hostMetricsList);
}
Storing results
In your main
you're storing the hostMetricsList
only to print it when you're done. Why not simply print the metrics as you go and avoid the list all together?
-
\$\begingroup\$ I wouldn't say putting an object into a usable state is RAII - that's just what a constructor should be used for - and
initialize
methods are not a symptom of not using RAII but not understanding the purpose of constructors IMO. Don't get me wrong, promoting RAII is great and all, but what you've suggested here isn't really using RAII - RAII is is the idea that when you acquire an object you acquire all of it's dependant resources, and when you deterministically delete the object all of it's resources are released. \$\endgroup\$Dan– Dan2014年08月21日 20:21:48 +00:00Commented Aug 21, 2014 at 20:21 -
\$\begingroup\$ True, I went overboard with RAII. But generally I see a "initialize" method as a codesmell. \$\endgroup\$Emily L.– Emily L.2014年08月21日 23:13:28 +00:00Commented Aug 21, 2014 at 23:13
-
\$\begingroup\$ That I agree with :P \$\endgroup\$Dan– Dan2014年08月22日 07:32:19 +00:00Commented Aug 22, 2014 at 7:32
-
\$\begingroup\$ @EmilyL. Thanks a lot for your suggestion. Sorry I was out on vacation for a week so not able to work on your suggestion. I have started working now and your suggestion looks great to me. One thing I am not clear is as you said, I can make
constructMetricUrl
method inHostMetrics
class itself but I am not sure how would that method be called and from where inHostMetrics
class? And also where I would add my serialization code example as shown in the question. Can you provide an example how would myHostMetrics
class would look like? \$\endgroup\$arsenal– arsenal2014年08月29日 21:53:34 +00:00Commented Aug 29, 2014 at 21:53 -
\$\begingroup\$ You need the metric URL in all cases as far as I can tell so you can just call it from the constructor of
HostMetrics
. After the constructor is executed theHostMetrics
most of the work would be done already. You would just basically move most of the code in the main method to the constructor ofHostMetrics
and preferrably split it into smaller functions that are easy to understand. \$\endgroup\$Emily L.– Emily L.2014年08月30日 16:53:10 +00:00Commented Aug 30, 2014 at 16:53
In addition to what Emily L. says, make class Dimensions
static or move it into its own file as a top level class.
If this applies depends on your serialisation mechanism, still:
If you don't, a new additional Dimensions
class will be loaded for each instance of DataTransactionMetrics
.
Loading and garbage collecting classes is no small task. The application performance will probably improve if you make it static.