let me please know what could be improved here. I've developed backend + frontend solution for the following task:
Back-end:
Create a simple Spring Web MVC application and manage dependencies through Maven. Set up an endpoint on the app that accepts
POST
/GET
requests on<endpoint>/data?size=<X>
, where the size parameter is optional. This endpoint should return a list of JSON objects. Size of the list is governed by thesize
param and defaults to 3. The response should be in the following format:
[{
"id": <randomly generated UUID>,
"tomatoes": <randomly generated Integer from 1 to 2000>,
“provider": <randomly generated String, one of the following:
"Heinz", "Hunt's", "Del Monte", "Le Ol' Granma">,
“timestamp": <randomly-generated long from the beginning of this year to today>
}, ... ]
Front-end:
Make a single page application that queries the previously created Spring app. Provide a button to refresh the data and an input to change the size of the request. The app should just render the list of results received from the backend in a table which has the name of the provider, number of tomatoes, and the date of the sale.
Solution: The whole code is here on github. It would be great if focus was on the Java back-end side primarily, though any improvements on front-end are also welcome.
Resource:
package com.dzmitryh.tomatobackend.controller;
import com.dzmitryh.tomatobackend.domain.Sale;
import com.dzmitryh.tomatobackend.domain.Provider;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.IntStream;
import static com.dzmitryh.tomatobackend.util.DatesUtil.getDatesBetween;
import static com.dzmitryh.tomatobackend.util.DatesUtil.toEpochMilli;
import static java.util.stream.Collectors.toList;
@RestController
@RequestMapping("sales")
public class SalesResource {
private static final int TOMATOES_UPPER_BOUND = 2000;
private static final int CURRENT_YEAR = 2017;
private static final int PROVIDERS_COUNT = Provider.values().length;
@RequestMapping("/data")
public List<Sale> get(@RequestParam(value = "size", required = false, defaultValue = "3") int size) {
return buildSalesList(size);
}
private List<Sale> buildSalesList(int size) {
return IntStream.range(0, size)
.mapToObj(item -> {
LocalDate startDate = LocalDate.ofYearDay(CURRENT_YEAR, 1);
LocalDate endDate = LocalDate.now();
List<LocalDate> datesBetween = getDatesBetween(startDate, endDate);
return Sale.create(
UUID.randomUUID().toString(),
new Random().nextInt(TOMATOES_UPPER_BOUND),
Provider.values()[new Random().nextInt(PROVIDERS_COUNT)].getStrValue(),
toEpochMilli(datesBetween.get(new Random().nextInt(datesBetween.size())))
);
})
.collect(toList());
}
}
Model:
package com.dzmitryh.tomatobackend.domain;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import javax.annotation.Nonnull;
@AutoValue
public abstract class Sale {
@JsonProperty
public abstract String id();
@JsonProperty
public abstract int tomatoes();
@JsonProperty
public abstract String provider();
@JsonProperty
public abstract long timestamp();
@JsonCreator
public static Sale create(@Nonnull String id, int tomatoes,
@Nonnull String provider, long timestamp) {
return new AutoValue_Sale(id, tomatoes, provider, timestamp);
}
}
package com.dzmitryh.tomatobackend.domain;
import static com.google.common.base.MoreObjects.toStringHelper;
public enum Provider {
HEINZ("Heinz"),
HUNTS("Hunt's"),
DEL_MONTE("Del Monte"),
LE_OL_GRANMA("Le Ol' Granma");
private final String strValue;
Provider(String strValue) {
this.strValue = strValue;
}
public String getStrValue() {
return strValue;
}
@Override
public String toString() {
return toStringHelper(this)
.add("strValue", strValue)
.toString();
}
}
Util:
package com.dzmitryh.tomatobackend.util;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
public final class DatesUtil {
private DatesUtil() {
}
public static List<LocalDate> getDatesBetween(LocalDate startDate, LocalDate endDate) {
return Stream.iterate(startDate, date -> date.plusDays(1))
.limit(ChronoUnit.DAYS.between(startDate, endDate))
.collect(toList());
}
public static long toEpochMilli(LocalDate localDate) {
return localDate.atStartOfDay().atZone(ZoneOffset.UTC)
.toInstant().toEpochMilli();
}
}
Frontend:
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(
<App />, document.getElementById('root')
);
registerServiceWorker();
App.js:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
sales: [],
input: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
this.callApi();
}
callApi(size) {
let request = "http://localhost:8080/sales/data";
if (size) {
request += "?size=" + size;
}
axios.get(request)
.then(res => {
this.setState({sales: res.data});
});
}
handleChange(e) {
this.setState({input: e.target.value});
}
handleClick() {
this.callApi(this.state.input);
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Tomato Business inc.</h1>
</header>
<div className="sales-control">
<span>Size:</span>
<input id="textInput" type="text" onChange={this.handleChange}/>
<input
id="submit"
type="button"
value="Refresh"
onClick={this.handleClick}
/>
</div>
<table className="sales">
<tbody>
<tr>
<th>Provider</th>
<th>Tomatoes</th>
<th>Sale date</th>
</tr>
{this.state.sales.map(sale =>
<tr key={sale.id}>
<td>{sale.provider}</td>
<td>{sale.tomatoes}</td>
<td>{new Date(sale.timestamp).toLocaleDateString()}</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
export default App;
map()
is complicated. Make it more simple. Break into methods. \$\endgroup\$componentWillMount
instead ofcomponentDidMount
as you are not accessing any dom \$\endgroup\$