0
\$\begingroup\$

The following API caters to CRUD operations on users and their corresponding filters (kind of rules). These filters are applied on incoming data from various sources basing on user and then a variety of transformations are done to the data.

I would like the reviewers to comment on design, endpoints, exception handling, and request/response. Since I am learning to design REST APIs, your comments will help me in writing better APIs in the future.

The Data.class creates a static list of user objects and all the CRUD operations are performed on this object and serialized/deserialized from/to file.

The reason for choosing a file to DB is right now the data is very small and the project is in a POC phase.

package com.cisco.daas.filter.services;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.cisco.daas.configservice.entities.User;
import com.cisco.daas.configservice.utilities.FileOperations;
import com.cisco.daas.configservice.utilities.Operations;

public class Data {

    private static final Operations<User> operations = new FileOperations<User>();

    private static final List<User> users = new ArrayList<>();

    public static List<User> getUsers() {
        users.clear();
        try {
            users.addAll(operations.get());
        } catch (IOException e) {
            e.getMessage();
        }
        return users;
    }
}

Interfaces: The reason for commenting the methods to create list of entities at once is because I felt it is not elegant. Please let me know if the thinking is wrong.

package com.cisco.daas.filter.services;

import java.util.List;

import com.cisco.daas.configservice.entities.Filter;

public interface IFilterService {

    void add(String userName,Filter filter);

    //void add(String userName,List<Filter> filters);

    Filter update(String userName, Filter filter);

    void remove(String userName, String filterName);

    List<Filter> getAll(String userName);

    Filter getOne(String userName,String filterName); 

}


package com.cisco.daas.filter.services;

import java.util.List;

import com.cisco.daas.configservice.entities.User;

public interface IUserService {

    User add(User item);

    //List<User> add(List<User> items);

    User update(User item);

    String remove(String userID);

    List<User> query(String userID);

    List<User> query();

}

Implementations:

package com.cisco.daas.filter.services;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.cisco.daas.configservice.entities.Filter;
import com.cisco.daas.configservice.entities.User;
import com.cisco.daas.configservice.utilities.FileOperations;
import com.cisco.daas.configservice.utilities.Operations;

@Service
public class FilterService implements IFilterService {

    private List<User> users = Data.getUsers();
    private static final Operations<User> operations = new FileOperations<User>();

    public FilterService() {

    }

    public void add(String userID, Filter filter) {
        users.stream().filter(u -> u.getUserId().equals(userID)).forEach(x -> {
            x.getFilters().add(filter);
        });

        try {
            operations.save(users);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

//  public void add(String userID, List<Filter> filters) {
//      users.stream().filter(u -> u.getUserId().equals(userID)).forEach(x -> {
//          x.getFilters().addAll(filters);
//      });
//
//      try {
//          operations.save(users);
//      } catch (IOException e) {
//          // TODO Auto-generated catch block
//          e.printStackTrace();
//      }
//
//  }

    public Filter update(String userID, Filter filter) {
        users.stream().filter(element -> element.getUserId().equals(userID) && element.getFilters().stream()
                .filter(f -> f.getName().equals(filter.getName())).findAny().isPresent()).forEach(x -> {
                    x.getFilters().stream().forEach(s -> {
                        s.setCreatedOn(filter.getCreatedOn());
                        s.setDescription(filter.getDescription());
                        s.setEnabled(filter.isEnabled());
                        s.setFilterValue(filter.getFilterValue());
                        s.setReadOnly(filter.isReadOnly());
                        s.setModifiedOn(filter.getModifiedOn());
                        s.setName(filter.getName());
                        s.setType(filter.getType());
                        s.setValid(filter.isValid());
                        s.setEncrypted(filter.isEncrypted());
                    });
                });

        try {
            operations.save(users);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return filter;
    }

    public void remove(String userID, String filterName) {
        users.removeIf(element -> element.getUserId().equals(userID) && element.getFilters().stream()
                .filter(f -> f.getName().equals(filterName)).findAny().isPresent());

        try {
            operations.save(users);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public List<Filter> getAll(String userID) {
        List<Filter> filters = users.stream().map(User::getFilters).flatMap(List::stream).distinct()
                .collect(Collectors.toList());
        return filters;
    }

    public Filter getOne(String userID, String filterName) {
        java.util.function.Predicate<Filter> pred = p -> p.getName().equals(filterName);
        List<Filter> filters = users.stream().map(User::getFilters).flatMap(List::stream).distinct()
                .collect(Collectors.toList());
        Optional<Filter> f = filters.stream().filter(pred).findFirst();
        return f.get();
    }

}
package com.cisco.daas.filter.services;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.cisco.daas.configservice.entities.Filter;
import com.cisco.daas.configservice.entities.User;
import com.cisco.daas.configservice.utilities.FileOperations;
import com.cisco.daas.configservice.utilities.Operations;

@Service
public class UserService implements IUserService {

    private List<User> users = Data.getUsers();
    private static final Operations<User> operations = new FileOperations<User>();

    public UserService() {
    }

    public User add(User item) {

        Optional<User> result = users.stream().filter(x -> item.getUserId().equals(x.getUserId())).findFirst();
        if (!result.isPresent() || result == null) {

            users.add(item);
            for (User user : users) {
                try {
                    operations.save(user);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            System.out.println("User already exist");
        }
        return item;
    }

//  public List<User> add(List<User> items) {
//      Set<String> userids = items.stream().map(x -> x.getUserId()).collect(Collectors.toSet());
//      List<User> present = users.stream().filter(x -> userids.contains(x.getUserId())).collect(Collectors.toList());
//      List<User> notPresent = users.stream().filter(x -> !userids.contains(x.getUserId()))
//              .collect(Collectors.toList());
//
//      if (present.isEmpty()) {
//          users.addAll(items);
//          try {
//              operations.save(users);
//          } catch (IOException e) {
//              e.printStackTrace();
//          }
//      } else if (!notPresent.isEmpty()) {
//          users.addAll(notPresent);
//          try {
//              operations.save(users);
//          } catch (IOException e) {
//              e.printStackTrace();
//          }
//      }
//      return items;
//
//  }

    public User update(User item) {
        users.forEach(element -> {
            if (element.getUserId() == item.getUserId()) {
                element.setUserId(item.getUserId());
                element.setUserName(item.getUserName());
                element.getFilters().forEach(filter -> {
                    Set<String> filters = item.getFilters().stream().map(f -> f.getName()).collect(Collectors.toSet());
                    List<Filter> present = item.getFilters().stream().filter(x -> filters.contains(x.getName()))
                            .collect(Collectors.toList());
                    List<Filter> notPresent = item.getFilters().stream().filter(x -> !filters.contains(x.getName()))
                            .collect(Collectors.toList());
                    if (present.isEmpty()) {
                        element.setFilters(item.getFilters());
                    } else {
                        element.getFilters().stream().forEach(filt -> {
                            present.stream().forEach(x -> {
                                if (filt.getName() == x.getName()) {
                                    filt.setName(x.getName());
                                    filt.setFilterValue(x.getFilterValue());
                                    filt.setCreatedOn(x.getCreatedOn());
                                    filt.setModifiedOn(x.getModifiedOn());
                                    filt.setEnabled(x.isEnabled());
                                    filt.setEncrypted(x.isEncrypted());
                                    filt.setReadOnly(x.isReadOnly());
                                    filt.setType(x.getType());
                                    filt.setValid(x.isValid());
                                    filt.setDescription(x.getDescription());
                                }
                            });
                        });
                    }

                    if (!notPresent.isEmpty()) {
                        element.setFilters(notPresent);
                    }
                });
            }
        });

        try {
            operations.save(users);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return item;
    }

    public String remove(String userID) {
        users.removeIf(x -> x.getUserId().equals(userID));
        try {
            operations.save(users);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return userID;
    }

    public List<User> query(String userID) {
        List<User> result = null;
        if (userID == null) {
            System.out.println("User ID cannot be empty");
        } else {
            result = users.stream().filter(e -> e.getUserId().equals(userID)).collect(Collectors.toList());
        }
        return result;
    }

    public List<User> query() {
        return users;
    }

}

Utility classes to perform operations on the file like serializing/deserializing JSON:

package com.cisco.daas.configservice.utilities;

import java.io.IOException;
import java.util.List;

public interface Operations<T> {

    void save(T serializableObject) throws IOException;
    void save(List<T> serializableObjects) throws IOException;
    List<T> get() throws IOException;

}


package com.cisco.daas.configservice.utilities;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.cisco.daas.configservice.entities.User;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;

public class FileOperations<T> implements Operations<T> {

    private ObjectMapper objectMapper;
    // this is going to come from the property file.
    private String filePath = "C:\\Users\\phyadavi\\workspace\\ConfigurationService\\src\\main\\resources\\seed.txt";
    private File file;
    // private T targetClass;

    public FileOperations() {
        this.objectMapper = new ObjectMapper();
        this.file = new File(filePath);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.getMessage();
            }
        }
    }

    private void saveImpl(Object serializableObject) throws IOException {
        String jsonData = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(serializableObject);
        BufferedWriter br = new BufferedWriter(new FileWriter(file));
        br.write(jsonData);
        br.close();
    }

    @Override
    public void save(List<T> serializableObjects) throws IOException {
        saveImpl(serializableObjects);
    }

    @Override
    public void save(T serializableObject) throws IOException {
        saveImpl(serializableObject);
    }

    @Override
    public List<T> get() throws IOException {
        // JavaType type =
        // objectMapper.getTypeFactory().constructCollectionType(ArrayList.class,
        // targetClass.getClass());
        // List<T> list = objectMapper.readValue(file, type);
        List<T> list = objectMapper.readValue(file, new TypeReference<List<User>>() {
        });
        if (list == null) {
            List<T> list1 = new ArrayList<T>();
            return list1;
        }
        return list;
    }

}

Entities:

package com.cisco.daas.configservice.entities;

import java.util.UUID;

public interface Entity {   
    String id = UUID.randomUUID().toString();
}


package com.cisco.daas.configservice.entities;

import java.util.List;

public class User implements Entity {

    public User() {
    }

    private String userName;
    private String userId;
    private List<Filter> filters;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public List<Filter> getFilters() {
        return filters;
    }

    public void setFilters(List<Filter> filters) {
        this.filters = filters;
    }

}


package com.cisco.daas.configservice.entities;

import java.util.Date;

public class Filter {

    private String name;
    private FilterValue value;
    private String type;
    private boolean encrypted;
    private boolean readOnly;
    private Date createdOn;
    private Date modifiedOn;
    private boolean valid;
    private boolean enabled;
    private String description;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public FilterValue getFilterValue() {
        return value;
    }

    public void setFilterValue(FilterValue value) {
        this.value = value;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public boolean isEncrypted() {
        return encrypted;
    }

    public void setEncrypted(boolean encrypted) {
        this.encrypted = encrypted;
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
    }

    public Date getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
    }

    public Date getModifiedOn() {
        return modifiedOn;
    }

    public void setModifiedOn(Date modifiedOn) {
        this.modifiedOn = modifiedOn;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

}


package com.cisco.daas.configservice.entities;

public class FilterValue {

    private String filterId;
    private String filterName;
    private Condition condition;
    private boolean action;

    public String getFilterId() {
        return filterId;
    }

    public void setFilterId(String filterId) {
        this.filterId = filterId;
    }

    public String getFilterName() {
        return filterName;
    }

    public void setFilterName(String filterName) {
        this.filterName = filterName;
    }

    public Condition getCondition() {
        return condition;
    }

    public void setCondition(Condition condition) {
        this.condition = condition;
    }

    public boolean isAction() {
        return action;
    }

    public void setAction(boolean action) {
        this.action = action;
    }
}


package com.cisco.daas.configservice.entities;

public class Condition {

    private String field;
    private Criteria criteria;
    private String value;
    private boolean isAdhering;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public Criteria getCriteria() {
        return criteria;
    }

    public void setCriteria(Criteria criteria) {
        this.criteria = criteria;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public boolean isAdhering() {
        return isAdhering;
    }

    public void setAdhering(boolean isAdhering) {
        this.isAdhering = isAdhering;
    }
}


package com.cisco.daas.configservice.entities;

public enum Criteria {

    IS, 
    CONTAINS, 
    GREATER_THAN, 
    LESSER_THAN;

}

Controllers:

package com.cisco.daas.configservice.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import com.cisco.daas.configservice.entities.User;
import com.cisco.daas.filter.services.IUserService;

@RestController
@RequestMapping("/api")
public class UserController {

    @Autowired
    private IUserService userService;

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public @ResponseBody List<User> getAll() {
        return userService.query();
    }

    @RequestMapping(value = "/users/{id}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
    public @ResponseBody List<User> getUser(@PathVariable(value = "id") String userid) {
        return userService.query(userid);
    }

    @RequestMapping(value = "/users", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<HttpStatus> createUser(@Valid @RequestBody User user) {
        userService.add(user);
        return ResponseEntity.ok(HttpStatus.OK);
    }

//  @RequestMapping(value = "/users", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
//  public ResponseEntity<HttpStatus> createUsers(@Valid @RequestBody List<User> entities) {
//      @SuppressWarnings("unused")
//      List<User> users = userService.add(entities);
//      return ResponseEntity.ok(HttpStatus.OK);
//  }

    @RequestMapping(value = "/users", method = RequestMethod.PUT, produces = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<User> updateUser(@Valid @RequestBody User entity) {
        User user = userService.update(entity);
        if (user == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(user);
    }

    @RequestMapping(value = "/userid/{id}", method = RequestMethod.DELETE, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<HttpStatus> deleteUser(@PathVariable(value = "id") String userid) {
        String result = userService.remove(userid);
        if (result.equals(null)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(HttpStatus.OK);
    }

}


package com.cisco.daas.configservice.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.cisco.daas.configservice.entities.Filter;
import com.cisco.daas.filter.services.IFilterService;

@RestController
@RequestMapping("/api")
public class FilterController {

    @Autowired
    private IFilterService filterService;

    @RequestMapping(value = "/users/{id}/filters", method = RequestMethod.GET, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public @ResponseBody List<Filter> getAll(@PathVariable(value = "id") String userID) {
        return filterService.getAll(userID);
    }

    @RequestMapping(value = "/users/{id}/filters/{filterName}", method = RequestMethod.GET, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<Filter> getFilter(@RequestParam(value = "id", required = true) String userID,
            @PathVariable(value = "filterName") String filterName) {
        Filter filter = filterService.getOne(userID, filterName);
        if (filter == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(filter);
    }

    @RequestMapping(value = "/users/{id}/filters", method = RequestMethod.POST, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<HttpStatus> createFilter(@PathVariable(value = "id") String id,
            @Valid @RequestBody Filter filter) {
        filterService.add(id, filter);
        return ResponseEntity.ok(HttpStatus.OK);
    }

//  @RequestMapping(value = "/users/{userID}/filters", method = RequestMethod.POST, produces = {
//          MediaType.APPLICATION_JSON_VALUE })
//  public ResponseEntity<HttpStatus> createFilters(@PathVariable(value = "id") String userID,
//          @Valid @RequestBody List<Filter> filters) {
//      filterService.add(userID, filters);
//      return ResponseEntity.ok(HttpStatus.OK);
//  }

    @RequestMapping(value = "/users/{userID}/filters", method = RequestMethod.PUT, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<Filter> updateUser(@PathVariable(value = "id") String userID,
            @Valid @RequestBody Filter entity) {
        Filter filter = filterService.update(userID, entity);
        if (filter == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(filter);
    }

    @RequestMapping(value = "/userid/{id}/filters/{filterName}", method = RequestMethod.DELETE, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<HttpStatus> deleteUser(@PathVariable(value = "id") String userid,
            @PathVariable(value="filterName") String filterName) {
        filterService.remove(userid, filterName);
        return ResponseEntity.ok(HttpStatus.OK);
    }
}

Main class:

package com.cisco.daas;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages="com.cisco.daas")
public class ConfigurationServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigurationServiceApplication.class, args);
    }
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I hope this falls under the realms of Design as you requested. These are the only suggestions I could think of. I have no real helpful comments regarding your endpoints or exception handling, as those seem quite straightforward and I see nothing wrong.

I would recommend replacing field Autowired with constructor injection. Instead of:

@Autowired
private IFilterService filterService;

use:

private final IFilterService filterService;

@Autowired
public FilterController (IFilterService filterService) {
    this.filterService = filterService;
}

This is explained here: http://olivergierke.de/2013/11/why-field-injection-is-evil/


You could also replace @RequestMapping(method = RequestMethod.PUT) with @PutMapping, as it's a bit easier to instantly see which method is used, although they both work exactly the same.


I'd recommend looking into https://projectlombok.org/, to let yourself add class level annotations like @Getter @Setter and save some boilerplate code. It has a bunch of other features, the site has more information. (Note: JDK9 still isn't supported as far as I know)

\$\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.