这是Project Student的一部分。 其他帖子包括带有Jersey的Webservice Client,带有Jersey的 Webservice Server和带有Spring Data的Persistence 。
RESTful Webapp洋葱的第三层是业务层。 这就是应用程序的精髓所在–编写良好的持久性和Web服务层受到约束,但是任何事情都在业务层中进行。
此时我们仅实现CRUD方法,因此这些方法非常简单。
设计决策
Spring Data –我们将使用Spring Data作为持久层,因此我们需要牢记指定持久层接口。 正如我们将在下一个博客中看到的那样,这极大地简化了我们的生活。
局限性
DataAccessException –不会尝试根据我们收到的DataAccessException的类型抛出不同的异常。 稍后这将很重要–我们应该以不同于丢失数据库连接的方式对待约束违反。
服务介面
首先提醒我们在上一篇文章中确定的服务接口。
public interface CourseService {List<Course> findAllCourses();Course findCourseById(Integer id);Course findCourseByUuid(String uuid);Course createCourse(String name);Course updateCourse(Course course, String name);void deleteCourse(String uuid);
}
服务实施
由于服务是基本的CRUD操作,因此该服务的实施速度很快。 我们唯一需要担心的是数据库是否存在问题(Spring中的DataAccessException)或找不到期望值。 我们需要在前者的API中添加新的例外,而后者的API中已经有例外。
public class CourseServiceImpl implements CourseService {private static final Logger log = LoggerFactory.getLogger(CourseServiceImpl.class);@Resourceprivate CourseRepository courseRepository;/*** Default constructor*/public CourseServiceImpl() {}/*** Constructor used in unit tests*/CourseServiceImpl(CourseRepository courseRepository) {this.courseRepository = courseRepository;}/*** @see com.invariantproperties.sandbox.student.business.CourseService#* findAllCourses()*/@Transactional(readOnly = true)@Overridepublic List<Course> findAllCourses() {List<Course> courses = null;try {courses = courseRepository.findAll();} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("error loading list of courses: " + e.getMessage(), e);}throw new PersistenceException("unable to get list of courses.", e);}return courses;}/*** @see com.invariantproperties.sandbox.student.business.CourseService#* findCourseById(java.lang.Integer)*/@Transactional(readOnly = true)@Overridepublic Course findCourseById(Integer id) {Course course = null;try {course = courseRepository.findOne(id);} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("internal error retrieving course: " + id, e);}throw new PersistenceException("unable to find course by id", e, id);}if (course == null) {throw new ObjectNotFoundException(id);}return course;}/*** @see com.invariantproperties.sandbox.student.business.CourseService#* findCourseByUuid(java.lang.String)*/@Transactional(readOnly = true)@Overridepublic Course findCourseByUuid(String uuid) {Course course = null;try {course = courseRepository.findCourseByUuid(uuid);} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("internal error retrieving course: " + uuid, e);}throw new PersistenceException("unable to find course by uuid", e,uuid);}if (course == null) {throw new ObjectNotFoundException(uuid);}return course;}/*** @see com.invariantproperties.sandbox.student.business.CourseService#* createCourse(java.lang.String)*/@Transactional@Overridepublic Course createCourse(String name) {final Course course = new Course();course.setName(name);Course actual = null;try {actual = courseRepository.saveAndFlush(course);} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("internal error retrieving course: " + name, e);}throw new PersistenceException("unable to create course", e);}return actual;}/*** @see com.invariantproperties.sandbox.course.persistence.CourseService#* updateCourse(com.invariantproperties.sandbox.course.domain.Course,* java.lang.String)*/@Transactional@Overridepublic Course updateCourse(Course course, String name) {Course updated = null;try {final Course actual = courseRepository.findCourseByUuid(course.getUuid());if (actual == null) {log.debug("did not find course: " + course.getUuid());throw new ObjectNotFoundException(course.getUuid());}actual.setName(name);updated = courseRepository.saveAndFlush(actual);course.setName(name);} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("internal error deleting course: " + course.getUuid(),e);}throw new PersistenceException("unable to delete course", e,course.getUuid());}return updated;}/*** @see com.invariantproperties.sandbox.student.business.CourseService#* deleteCourse(java.lang.String)*/@Transactional@Overridepublic void deleteCourse(String uuid) {Course course = null;try {course = courseRepository.findCourseByUuid(uuid);if (course == null) {log.debug("did not find course: " + uuid);throw new ObjectNotFoundException(uuid);}courseRepository.delete(course);} catch (DataAccessException e) {if (!(e instanceof UnitTestException)) {log.info("internal error deleting course: " + uuid, e);}throw new PersistenceException("unable to delete course", e, uuid);}}
}
此实现告诉我们持久层所需的接口。
持久层接口
我们将使用Spring Data作为持久层,并且DAO接口与Spring Data存储库相同。 我们只需要一种非标准方法。
public class CourseServiceImplTest {@Testpublic void testFindAllCourses() {final List<Course> expected = Collections.emptyList();final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findAll()).thenReturn(expected);final CourseService service = new CourseServiceImpl(repository);final List<Course> actual = service.findAllCourses();assertEquals(expected, actual);}@Test(expected = PersistenceException.class)public void testFindAllCoursesError() {final List<Course> expected = Collections.emptyList();final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findAll()).thenThrow(new UnitTestException());final CourseService service = new CourseServiceImpl(repository);final List<Course> actual = service.findAllCourses();assertEquals(expected, actual);}@Testpublic void testFindCourseById() {final Course expected = new Course();expected.setId(1);final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findOne(any(Integer.class))).thenReturn(expected);final CourseService service = new CourseServiceImpl(repository);final Course actual = service.findCourseById(expected.getId());assertEquals(expected, actual);}@Test(expected = ObjectNotFoundException.class)public void testFindCourseByIdMissing() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findOne(any(Integer.class))).thenReturn(null);final CourseService service = new CourseServiceImpl(repository);service.findCourseById(1);}@Test(expected = PersistenceException.class)public void testFindCourseByIdError() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findOne(any(Integer.class))).thenThrow(new UnitTestException());final CourseService service = new CourseServiceImpl(repository);service.findCourseById(1);}@Testpublic void testFindCourseByUuid() {final Course expected = new Course();expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(expected);final CourseService service = new CourseServiceImpl(repository);final Course actual = service.findCourseByUuid(expected.getUuid());assertEquals(expected, actual);}@Test(expected = ObjectNotFoundException.class)public void testFindCourseByUuidMissing() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(null);final CourseService service = new CourseServiceImpl(repository);service.findCourseByUuid("[uuid]");}@Test(expected = PersistenceException.class)public void testFindCourseByUuidError() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenThrow(new UnitTestException());final CourseService service = new CourseServiceImpl(repository);service.findCourseByUuid("[uuid]");}@Testpublic void testCreateCourse() {final Course expected = new Course();expected.setName("name");expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.saveAndFlush(any(Course.class))).thenReturn(expected);final CourseService service = new CourseServiceImpl(repository);final Course actual = service.createCourse(expected.getName());assertEquals(expected, actual);}@Test(expected = PersistenceException.class)public void testCreateCourseError() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.saveAndFlush(any(Course.class))).thenThrow(new UnitTestException());final CourseService service = new CourseServiceImpl(repository);service.createCourse("name");}@Testpublic void testUpdateCourse() {final Course expected = new Course();expected.setName("Alice");expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(expected);when(repository.saveAndFlush(any(Course.class))).thenReturn(expected);final CourseService service = new CourseServiceImpl(repository);final Course actual = service.updateCourse(expected, "Bob");assertEquals("Bob", actual.getName());}@Test(expected = ObjectNotFoundException.class)public void testUpdateCourseMissing() {final Course expected = new Course();final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(null);final CourseService service = new CourseServiceImpl(repository);service.updateCourse(expected, "Bob");}@Test(expected = PersistenceException.class)public void testUpdateCourseError() {final Course expected = new Course();expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(expected);doThrow(new UnitTestException()).when(repository).saveAndFlush(any(Course.class));final CourseService service = new CourseServiceImpl(repository);service.updateCourse(expected, "Bob");}@Testpublic void testDeleteCourse() {final Course expected = new Course();expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(expected);doNothing().when(repository).delete(any(Course.class));final CourseService service = new CourseServiceImpl(repository);service.deleteCourse(expected.getUuid());}@Test(expected = ObjectNotFoundException.class)public void testDeleteCourseMissing() {final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(null);final CourseService service = new CourseServiceImpl(repository);service.deleteCourse("[uuid]");}@Test(expected = PersistenceException.class)public void testDeleteCourseError() {final Course expected = new Course();expected.setUuid("[uuid]");final CourseRepository repository = Mockito.mock(CourseRepository.class);when(repository.findCourseByUuid(any(String.class))).thenReturn(expected);doThrow(new UnitTestException()).when(repository).delete(any(Course.class));final CourseService service = new CourseServiceImpl(repository);service.deleteCourse(expected.getUuid());}
}
整合测试
集成测试已推迟到实现持久层。
源代码
- 可从http://code.google.com/p/invariant-properties-blog/source/browse/student/student-business获得源代码。
翻译自: https://www.javacodegeeks.com/2014/01/project-student-business-layer.html