3
\$\begingroup\$

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 the size 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;
\$\endgroup\$
5
  • \$\begingroup\$ Your map() is complicated. Make it more simple. Break into methods. \$\endgroup\$ Oct 24, 2017 at 5:24
  • \$\begingroup\$ Might be more efficient to use componentWillMount instead of componentDidMount as you are not accessing any dom \$\endgroup\$
    – August
    Oct 24, 2017 at 7:59
  • \$\begingroup\$ @JudeNiroshan, thanks perhaps some service layer abstraction should work it out. \$\endgroup\$ Oct 24, 2017 at 9:30
  • \$\begingroup\$ @August nice catch. Will do. \$\endgroup\$ Oct 24, 2017 at 9:30
  • \$\begingroup\$ You should have a look to the Spring tutorial to match their conventions and naming. In a real-life situation the datas are received from a repository, you can create one that hide all the random logic to keep your controller away of those things. \$\endgroup\$
    – gervais.b
    Oct 24, 2017 at 19:15

1 Answer 1

2
\$\begingroup\$

Ok, I think you move for nice code, but I would go for simplicity.

Create a simple Spring Web MVC application and manage dependencies through Maven.

  • I would go for spring boot - its much less configuration
  • maven dependencies - I guess no you have no problem with that.

Set up an endpoint on the app that accepts POST/GET requests on /data?size=, where the size parameter is optional.

  • Ok, it means that we return same "GET" scenario for both GET and POST? Your annotation probably should be:

    @RequestMapping(value = "/data", method = { RequestMethod.GET, RequestMethod.POST })
    

    This endpoint should return a list of JSON objects. The size of the list is governed by the size param and defaults to 3. The response should be in the following format:

  • ok, so iteration goes from 1 to amount of items specified, so it's basically a loop

  • probably you need to ignore values below 0, for zero just return an empty list. 1 is minimal I guess.

    [{"id": <randomly generated UUID>, -> UUID.randomUUID()
    "tomatoes": <randomly generated Integer from 1 to 2000>, 
    “provider": <randomly generated String, one of the following:  
          "Heinz", "Hunt's", "Del Monte", "Le Ol' Granma">,
    

    -> They probably don't need it nice, so could be just array of strings and random index, to keep it simple

    “timestamp": <randomly-generated long from the beginning of this year to today>  
    

    -> I don't think if you call method million times then time this year would change, so probably it might be cached for efficiency.

    random = Math.random*(System.currentTimeMilliseconds() - startOfTheYear)
    

If you go for a "nice" version, then parameters and providers would go into configuration.
I don't know if you were asked to go with simplest or "production-ready" code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.