Adding abstractions for CRUD APIs.

Now we can easily add controllers for any resource quickly by simply adding a few files.
This commit is contained in:
Arpit Mohan 2019-03-19 19:39:05 +05:30
parent f7aaafacfb
commit 8ccd001652
16 changed files with 252 additions and 101 deletions

View File

@ -27,15 +27,15 @@ public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
// .authenticationManager(reactiveAuthenticationManager)
// .securityContextRepository(securityContextRepository)
.authorizeExchange()
.anyExchange().permitAll()
.and()
.logout().disable()
.build();
.authorizeExchange()
.anyExchange().permitAll()
.and()
.logout().disable()
.build();
}
}

View File

@ -4,4 +4,5 @@ public interface Url {
String BASE_URL = "/api";
String VERSION = "/v1";
String WIDGET_URL = BASE_URL + VERSION + "/widgets";
String TENANT_URL = BASE_URL + VERSION + "/tenants";
}

View File

@ -1,4 +1,38 @@
package com.mobtools.server.controllers;
public abstract class BaseController {
import com.mobtools.server.domains.BaseDomain;
import com.mobtools.server.dtos.ResponseDto;
import com.mobtools.server.services.CrudService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
@RequiredArgsConstructor
public abstract class BaseController<S extends CrudService, T extends BaseDomain, ID> {
private final S service;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseDto<T>> create(@Valid @RequestBody T resource) {
return service.create(resource)
.map(created -> new ResponseDto<>(HttpStatus.CREATED.value(), created, null));
}
@GetMapping("")
public Flux<ResponseDto<T>> getAll() {
return service.get()
.map(resources -> new ResponseDto<>(HttpStatus.OK.value(), resources, null));
}
@PutMapping("/{id}")
public Mono<ResponseDto<T>> update(@PathVariable ID id, @RequestBody T resource) throws Exception {
return service.update(id, resource)
.map(updatedResource -> new ResponseDto<>(HttpStatus.OK.value(), updatedResource, null));
}
}

View File

@ -0,0 +1,31 @@
package com.mobtools.server.controllers;
import com.mobtools.server.constants.Url;
import com.mobtools.server.domains.Tenant;
import com.mobtools.server.dtos.ResponseDto;
import com.mobtools.server.services.TenantService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping(Url.TENANT_URL)
public class TenantController extends BaseController<TenantService, Tenant, String> {
private final TenantService tenantService;
public TenantController(TenantService tenantService) {
super(tenantService);
this.tenantService = tenantService;
}
@GetMapping("/{name}")
public Mono<ResponseDto<Tenant>> getByName(@PathVariable String name) {
return tenantService.getByName(name)
.map(widget -> new ResponseDto<>(HttpStatus.OK.value(), widget, null));
}
}

View File

@ -4,32 +4,22 @@ import com.mobtools.server.constants.Url;
import com.mobtools.server.domains.Widget;
import com.mobtools.server.dtos.ResponseDto;
import com.mobtools.server.services.WidgetService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
@RestController
@RequestMapping(Url.WIDGET_URL)
@RequiredArgsConstructor
public class WidgetController extends BaseController {
public class WidgetController extends BaseController<WidgetService, Widget, String> {
private final WidgetService widgetService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseDto<Widget>> create(@Valid @RequestBody Widget widget) {
return widgetService.create(widget)
.map(createdWidget -> new ResponseDto<>(HttpStatus.CREATED.value(), createdWidget, null));
}
@GetMapping("")
public Flux<ResponseDto<Widget>> getAllWidgets() {
return widgetService.get()
.map(widgets -> new ResponseDto<>(HttpStatus.OK.value(), widgets, null));
public WidgetController(WidgetService service) {
super(service);
this.widgetService = service;
}
@GetMapping("/{name}")
@ -37,11 +27,4 @@ public class WidgetController extends BaseController {
return widgetService.getByName(name)
.map(widget -> new ResponseDto<>(HttpStatus.OK.value(), widget, null));
}
@PutMapping("/{id}")
public Mono<ResponseDto<Widget>> update(@PathVariable String id, @RequestBody Widget widget) throws Exception {
return widgetService.update(id, widget)
.map(updatedWidget -> new ResponseDto<>(HttpStatus.OK.value(), updatedWidget, null));
}
}

View File

@ -0,0 +1,10 @@
package com.mobtools.server.repositories;
import com.mobtools.server.domains.Tenant;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
@Repository
public interface TenantRepository extends BaseRepository<Tenant, String> {
Mono<Tenant> findByName(String name);
}

View File

@ -0,0 +1,8 @@
package com.mobtools.server.repositories;
import com.mobtools.server.domains.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends BaseRepository<User, String> {
}

View File

@ -5,7 +5,7 @@ import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
@Repository
public interface WidgetRepository extends BaseRepository<Widget, Long> {
public interface WidgetRepository extends BaseRepository<Widget, String> {
Mono<Widget> findByName(String name);
}

View File

@ -1,10 +1,72 @@
package com.mobtools.server.services;
import lombok.RequiredArgsConstructor;
import com.mobtools.server.domains.BaseDomain;
import com.mobtools.server.repositories.BaseRepository;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
@RequiredArgsConstructor
public abstract class BaseService {
import java.util.Map;
public abstract class BaseService<R extends BaseRepository, T extends BaseDomain, ID> implements CrudService<T, ID> {
final Scheduler scheduler;
private final MongoConverter mongoConverter;
private final ReactiveMongoTemplate mongoTemplate;
private final R repository;
public BaseService(Scheduler scheduler,
MongoConverter mongoConverter,
ReactiveMongoTemplate reactiveMongoTemplate,
R repository) {
this.scheduler = scheduler;
this.mongoConverter = mongoConverter;
this.mongoTemplate = reactiveMongoTemplate;
this.repository = repository;
}
@Override
public Mono<T> update(ID id, T resource) throws Exception {
if (id == null) {
throw new Exception("Invalid id provided");
}
Query query = new Query(Criteria.where("id").is(id));
DBObject update = getDbObject(resource);
Update updateObj = new Update();
Map<String, Object> updateMap = update.toMap();
updateMap.entrySet().stream().forEach(entry -> updateObj.set(entry.getKey(), entry.getValue()));
return mongoTemplate.updateFirst(query, updateObj, resource.getClass())
.flatMap(obj -> repository.findById(id))
.subscribeOn(scheduler);
}
@Override
public Flux<T> get() {
return repository.findAll();
}
@Override
public Mono<T> create(T widget) {
return repository.save(widget);
}
private DBObject getDbObject(Object o) {
BasicDBObject basicDBObject = new BasicDBObject();
mongoConverter.write(o, basicDBObject);
return basicDBObject;
}
}

View File

@ -0,0 +1,14 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.BaseDomain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CrudService<T extends BaseDomain, ID> {
Flux<T> get();
Mono<T> create(T resource);
Mono<T> update(ID id, T resource) throws Exception;
}

View File

@ -0,0 +1,9 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.Tenant;
import reactor.core.publisher.Mono;
public interface TenantService extends CrudService<Tenant, String> {
Mono<Tenant> getByName(String name);
}

View File

@ -0,0 +1,30 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.Tenant;
import com.mobtools.server.repositories.TenantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
@Service
public class TenantServiceImpl extends BaseService<TenantRepository, Tenant, String> implements TenantService {
private final TenantRepository repository;
@Autowired
public TenantServiceImpl(Scheduler scheduler,
MongoConverter mongoConverter,
ReactiveMongoTemplate reactiveMongoTemplate,
TenantRepository repository) {
super(scheduler, mongoConverter, reactiveMongoTemplate, repository);
this.repository = repository;
}
@Override
public Mono<Tenant> getByName(String name) {
return repository.findByName(name);
}
}

View File

@ -0,0 +1,6 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.User;
public interface UserService extends CrudService<User, String> {
}

View File

@ -0,0 +1,19 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.User;
import com.mobtools.server.repositories.UserRepository;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.stereotype.Service;
import reactor.core.scheduler.Scheduler;
@Service
public class UserServiceImpl extends BaseService<UserRepository, User, String> implements UserService {
public UserServiceImpl(Scheduler scheduler,
MongoConverter mongoConverter,
ReactiveMongoTemplate reactiveMongoTemplate,
UserRepository repository) {
super(scheduler, mongoConverter, reactiveMongoTemplate, repository);
}
}

View File

@ -1,16 +1,9 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.Widget;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface WidgetService {
public interface WidgetService extends CrudService<Widget, String> {
Mono<Widget> getByName(String name);
Flux<Widget> get();
Mono<Widget> create(Widget widget);
Mono<Widget> update(String id, Widget widget) throws Exception;
}

View File

@ -2,81 +2,32 @@ package com.mobtools.server.services;
import com.mobtools.server.domains.Widget;
import com.mobtools.server.repositories.WidgetRepository;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
@Service
@Slf4j
public class WidgetServiceImpl extends BaseService implements WidgetService {
public class WidgetServiceImpl extends BaseService<WidgetRepository, Widget, String> implements WidgetService {
private WidgetRepository widgetRepository;
private final MongoConverter mongoConverter;
private final ReactiveMongoTemplate mongoTemplate;
@Autowired
public WidgetServiceImpl(Scheduler scheduler, WidgetRepository widgetRepository, MongoConverter mongoConverter, ReactiveMongoTemplate mongoTemplate) {
super(scheduler);
public WidgetServiceImpl(Scheduler scheduler,
MongoConverter mongoConverter,
ReactiveMongoTemplate mongoTemplate,
WidgetRepository widgetRepository) {
super(scheduler, mongoConverter, mongoTemplate, widgetRepository);
this.widgetRepository = widgetRepository;
this.mongoConverter = mongoConverter;
this.mongoTemplate = mongoTemplate;
}
@Override
public Mono<Widget> getByName(String name) {
return widgetRepository.findByName(name);
}
@Override
public Flux<Widget> get() {
return widgetRepository.findAll();
}
@Override
public Mono<Widget> create(Widget widget) {
return widgetRepository.save(widget);
}
@Override
public Mono<Widget> update(String id, Widget widget) throws Exception {
if (id == null) {
throw new Exception("Invalid id provided");
}
Query query = new Query();
query.addCriteria(Criteria.where("id").is(id));
Update updateObj = new Update();
// DBObject update = getDbObject(widget);
// Map<String, Object> updateMap = update.toMap();
// updateMap.entrySet().stream().forEach(entry -> {
// updateObj.set(entry.getKey(), entry.getValue());
// });
updateObj.set("name", "testName");
mongoTemplate.updateFirst(query,updateObj, Widget.class);
return widgetRepository.save(widget);
}
private DBObject getDbObject(Object o) {
BasicDBObject basicDBObject = new BasicDBObject();
mongoConverter.write(o, basicDBObject);
return basicDBObject;
}
}