Employing Java 8 in service layer

java8The purpose of this post is to take a look on lambda expressions introduced in Java SE 8 and using them in service layer making code more concise and sophisticated as well. The focus is put on services implementation. The CarService is introduced here as an example. Emphasis is put on Java8 lambda expressions, java.util.function package, especially on Predicate and Function. New features is a must in Java world. The possibilities do not bring new values in programming languages in general. They are already known from other languages. However, Java is a really general purpose programming language and it always takes time to introduce new features and ensuring production-ready platform. This post is code oriented. The most significant code snippets are provided throughout the content whereas the complete sources can be found on softexploration github.

Background

In the further content there is much more code than text. The code is itself commented enough. In addition to that there are also provided javadocs where necessary. You can find suggestions how Java 8 can change code and in result make it more readable, a little bit artistic and following logical thinking about problem solving. The content does not explain Java 8. Java 8 is just brought into code. Java 8 basics with examples can be found on [1] and the knowledge is here an assumption to read implementation. The code implements simple use cases to manage cars. CarService interface with its default implementation and DefaultCarServiceTest class are starting points to exploring code.

1. Use case

Before we go to implementation stage, the key solution parts are explained here.

CarService

The front interface is CarService. Its goal is to provide CRUD operations on Car entity. Some methods of this interface take Predicate as input parameters. Predicates play a filter role. Implementation start point is DefaultCarService class. Is has four references to delegating services (validator and processors).

Car

It is a simple car entity with the following attributes: make, model, year, engine and color where make and model create a compound mandatory identifier.

CarEntry

This is an auxiliary type which provides data in a raw format (array of String) to create Car entity. This type is introduced to show conversion in service processing phases. It can have real-world usage in controllers.

2. Implementation overview

In this part are delivered core interfaces and implementations. Sometimes code is only in a shorter, partial form. The complete source is available on [2]. I encourage to import whole code into IDE and start with CarService and DefaultCarServiceTest.

2.1. CarService


package com.softexploration.lab.cars.core.service;

// imports

/**
 * Service to manage cars
 */
public interface CarService {

	/**
	 * Creates a new car
	 *
	 * @param entry
	 *            - data to create a new instance
	 * @return new Car instance
	 */
	Car createCar(final CarEntry entry);

	/**
	 * Updates an existing Car instance
	 *
	 * @param entry
	 *            - data to update an instance
	 * @return updated instance
	 */
	Car updateCar(final CarEntry entry);

	/**
	 * Finds cars matching a predicate
	 *
	 * @param predicate
	 * @return cars matching a predicate
	 */
	List<Car> findCars(final Predicate<Car> predicate);

	/**
	 * Removes cars matching a predicate
	 *
	 * @param predicate
	 * @return cars after applying remove
	 */
	List<Car> removeCars(final Predicate<Car> predicate);

	/**
	 * @return all Car instances
	 */
	List<Car> getAllCars();
}

2.2. DefaultCarService

package com.softexploration.lab.cars.core.service.impl;

// imports

public class DefaultCarService implements CarService {

	private List<Car> carsRepository;

	private CarServiceValidator carServiceValidator;
	private CarServicePreProcessor carServicePreProcessor;
	private CarServiceCoreProcessor carServiceCoreProcessor;
	private CarServicePostProcessor carServicePostProcessor;

	// setters come here

	protected List<Car> getCars() {
		return Lists.newArrayList(carsRepository);
	}

	@Override
	public Car createCar(final CarEntry entry) {
		return carServicePostProcessor
			.create()
			.compose(carServiceCoreProcessor.create()
                        .compose(carServiceValidator.validateCarForCreate()
                        .compose(carServicePreProcessor.create()
                        .compose(carServiceValidator.validateCarEntryForCreate()))))
                        .apply(entry);
	}

	@Override
	public Car updateCar(final CarEntry entry) {
		return carServiceCoreProcessor
			.update()
			.compose(carServiceValidator.validateCarForUpdate()
                        .compose(carServicePreProcessor.update()
			.compose(carServiceValidator.validateCarEntryForUpdate())))
			.andThen(carServicePostProcessor.update()).apply(entry);
	}

	@Override
	public List<Car> findCars(final Predicate<Car> predicate) {
		return carsRepository.stream().filter(predicate).collect(Collectors.toList());
	}

	@Override
	public List<Car> removeCars(final Predicate<Car> predicate) {
		carsRepository.removeIf(predicate);
		return getCars();
	}

	@Override
	public List<Car> getAllCars() {
		return getCars();
	}
}

2.3. DefaultCarServiceValidator

package com.softexploration.lab.cars.core.service.validator.impl;

// imports

public class DefaultCarServiceValidator implements CarServiceValidator {

	private List<Car> carsRepository;

	// setters come here

	@Override
	public Function<CarEntry, CarEntry> validateCarEntryForCreate() {
		return validateCarEntry();
	}

	@Override
	public Function<CarEntry, CarEntry> validateCarEntryForUpdate() {
		return validateCarEntry();
	}

	protected Function<CarEntry, CarEntry> validateCarEntry() {
		return entry -> {
			if (validateCarEntryMandatoryContent().test(entry)) {
				return entry;
			} else {
				throw new InvalidCarEntryStructureException("CarEntry should provide 2 mandatory properties. Actual:"
						+ entry.getArray().length);
			}

		};
	}

	protected Predicate<CarEntry> validateCarEntryMandatoryContent() {
		return entry -> entry.getArray().length > 1;
	}

	@Override
	public Function<Car, Car> validateCarForUpdate() {
		return c -> {
			if (!carsRepository.contains(c)) {
				throw new CarNotFoundException(c.toString() + " does not exist");
			} else {
				return c;
			}
		};
	}

	@Override
	public Function<Car, Car> validateCarForCreate() {
		return c -> {
			if (carsRepository.contains(c)) {
				throw new DuplicateCarException("Car [make=" + c.getMake() + ", model=" + c.getModel()
						+ "] already exists");
			} else {
				return c;
			}
		};
	}
}

2.4. CarUtils


package com.softexploration.lab.cars.core.service.util;

// imports

public class CarUtils {

	public Predicate<Car> isGerman() {
		return c -> isAudi().or(isMercedes()).or(isOpel()).or(isPorsche()).or(isVw()).or(isBmw()).test(c);
	}

	public Predicate<Car> isAudi() {
		return c -> "audi".equalsIgnoreCase(c.getMake());
	}

	public Predicate<Car> isOpel() {
		return c -> "opel".equalsIgnoreCase(c.getMake());
	}

	public Predicate<Car> isMercedes() {
		return c -> "mercedes".equalsIgnoreCase(c.getMake());
	}

	public Predicate<Car> isPorsche() {
		return c -> "porsche".equalsIgnoreCase(c.getMake());
	}

	public Predicate<Car> isVw() {
		return c -> "volkswagen".equalsIgnoreCase(c.getMake());
	}

	public Predicate<Car> isBmw() {
		return c -> "bmw".equalsIgnoreCase(c.getMake());
	}

}

2.5. DefaultCarServiceTest


package com.softexploration.lab.cars.core.service.impl;

// imports

public class DefaultCarServiceTest {

	private CarService carService;
	private final CarUtils carUtils = new CarUtils();

	@Before
	public void setUp() {
		carService = null;
	}

	@After
	public void tearDown() {
		carService = null;
	}

	@Test(expected = InvalidCarEntryStructureException.class)
	public void testInvalidCarEntryStructure() {
		initCarService(Lists.newArrayList());

		final String[] input = { "mercedes" };
		getCarService().createCar(CarSampler.carEntry(input));
	}

	@Test
	public void testCreateCarMandatoryAttributes() {
		initCarService(Lists.newArrayList());

		final String[] input = { "mercedes", "e-klasse" };
		getCarService().createCar(CarSampler.carEntry(input));

		Assert.assertEquals(1, getCarService().getAllCars().size());
		Assert.assertThat(getCarService().getAllCars(), hasItem(CarSampler.car("mercedes", "e-klasse")));
	}

	@Test
	public void testCreateCarAllAttributes() {
		initCarService(Lists.newArrayList());

		final String[] input = { "opel", "astra", "2010", "1.7 CDTI", "silver" };
		getCarService().createCar(CarSampler.carEntry(input));

		Assert.assertEquals(1, getCarService().getAllCars().size());
		Assert.assertThat(getCarService().getAllCars(),
				hasItem(CarSampler.car("opel", "astra", Integer.valueOf("2010"), "1.7 CDTI", "silver")));
	}

	@Test(expected = DuplicateCarException.class)
	public void testCreateCarDuplicate() {
		initCarService(Lists
				.newArrayList(CarSampler.car("opel", "astra", Integer.valueOf("2010"), "1.7 CDTI", "silver")));

		final String[] input = { "opel", "astra", "2011", "1.9 CDTI", "black" };
		getCarService().createCar(CarSampler.carEntry(input));
	}

	@Test
	public void testUpdateCar() {
		initCarService(Lists.newArrayList(CarSampler.car("audi", "a6", Integer.valueOf("2007"), "3.0 TDI", "black"),
				CarSampler.car("opel", "astra", Integer.valueOf("2010"), "1.7 CDTI", "silver"),
				CarSampler.car("audi", "a4", Integer.valueOf("2008"), "2.0 TDI", "blue")));

		final String[] input = { "opel", "astra", "2011", "1.9 CDTI", "black" };
		final Car updatedObject = getCarService().updateCar(CarSampler.carEntry(input));

		// returned object has properly set attributes
		Assert.assertEquals(CarSampler.car("opel", "astra", Integer.valueOf("2010"), "1.9 CDTI", "black"),
				updatedObject);

		// updated object can be found and non-updated cars are untouched
		Assert.assertThat(
				getCarService().getAllCars(),
				hasItems(updatedObject, CarSampler.car("audi", "a4", Integer.valueOf("2008"), "2.0 TDI", "blue"),
						CarSampler.car("audi", "a6", Integer.valueOf("2007"), "3.0 TDI", "black")));

		// collection size is unchanged
		Assert.assertEquals(3, getCarService().getAllCars().size());
	}

	@Test(expected = CarNotFoundException.class)
	public void testUpdateCarNotFound() {
		initCarService(Lists
				.newArrayList(CarSampler.car("opel", "astra", Integer.valueOf("2010"), "1.7 CDTI", "silver")));

		final String[] input = { "opel", "vectra", "2011", "1.9 CDTI", "black" };
		getCarService().updateCar(CarSampler.carEntry(input));
	}

	@Test
	public void testFindGermanCars() {
		initCarService(CarSampler.createMiscellaneousCars());

		Assert.assertThat(CarSampler.createGermanCars(), equalTo(getCarService().findCars(carUtils.isGerman())));
	}

	@Test
	public void testRemoveCars() {
		initCarService(CarSampler.createMiscellaneousCars());

		Assert.assertThat(CarSampler.createGermanCars(),
				equalTo(getCarService().removeCars(carUtils.isGerman().negate())));
	}

	private void initCarService(final List<Car> cars) {
		final DefaultCarService service = new DefaultCarService();
		service.setCarsRepository(cars);
		service.setCarServicePreProcessor(new DefaultCarServicePreProcessor());
		final DefaultCarServiceCoreProcessor carServiceCoreProcessor = new DefaultCarServiceCoreProcessor();
		carServiceCoreProcessor.setCarsRepository(cars);
		service.setCarServiceCoreProcessor(carServiceCoreProcessor);
		service.setCarServicePostProcessor(new DefaultCarServicePostProcessor());
		final DefaultCarServiceValidator carServiceValidator = new DefaultCarServiceValidator();
		carServiceValidator.setCarsRepository(cars);
		service.setCarServiceValidator(carServiceValidator);
		carService = service;
	}

	private CarService getCarService() {
		return carService;
	}
}

3. Java 8 key points

Feature Feature occurrence/usage purposes
java.util.function.Predicate Filters:
CarService.findCars
CarService.removeCarsInformative,decision-making purposes:
CarUtils.is*methodsValidation purposes:
DefaultCarServiceValidator.validateCarEntryMandatoryContent
java.util.function.Function Core service processing, transformation purposes:
CarServiceCoreProcessor methodsPostprocessing,transformation purposes:
CarServicePostProcessor methodsPreprocessing, transformation purposes:
CarServicePreProcessor methodsVerification, validation purposes:
CarServiceValidator methodsFunctions composing: F.compose(), F.andThen
Functions chaining, invocation order establishing purposes:
DefaultCarService.createCar
DefaultCarService.updateCar
New forEach and streams Iteration, sequential transformation, searching:
DefaultCarService.findCars

4. Resources 

[1] http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html

[2] https://github.com/softexploration/softexploration-com.softexploration.lab.cars.core/tree/develop

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>