abandon your factories - data conversion with datus
Tags: java, functional, opensource
Published: 2019-06-07 11:46:50

(disclaimer: I am the author of [datus](https://github.com/roookeee/datus))

You are working with Java again, maybe you are developing a new (micro)service oriented architecture and are currently integrating another service, `PersonService`, into your `AService`. You receive a `PersonResource` and as you have learned you take that object and convert it into your own `Person` object to not rely on data structures that you don't own in your business logic:

```java
//external object
class PersonResource {
    //getters + setters omitted for brevity
    private String firstName;
    private String lastName;
}
//internal object
class Person {
    //getters + setters omitted for brevity
    private String firstName;
    private String lastName;
}
```

Let's build that handy factory / converter object:

```java
public PersonFactory {
    public Person create(PersonResource resource) {
      Person person = new Person();
      person.setFirstName(resource.getFirstName());
      person.setLastName(resource.setLastName());
      return person;
    }
}
```

That works, but what about the other 5-10 services and their 15-30 data objects? All this boiler plate is quite annoying, isn't it? And just now you realize a `PersonResource` can be null so your test coverage tool complains about not writing a test for such a trivial class.

What if there were a way to declare how the conversion is done, define how its done but not implement it ? Enter [datus](https://github.com/roookeee/datus):

```java
Mapper<PersonResource, Person> mapper = Datus.forTypes(PersonResource.class, Person.class)
    .mutable(Person::new)
    .from(PersonResource::getFirstName).into(Person::setFirstName)
    .from(PersonResource::getLastName).into(Person::setLastName)
    .build();
```
Pretty concise and self-explanatory - the declarative approach looks fine for now.
But what about that null check I was talking about? Let's implement that, but how?

```java
Mapper<PersonResource, Person> mapper = Datus.forTypes(PersonResource.class, Person.class)
    .mutable(Person::new)
    .from(PersonResource::getFirstName).into(Person::setFirstName)
    .from(PersonResource::getLastName).into(Person::setLastName)
    .build();

Mapper<PersonResource, Optional<Person>> notNullMapper = 
    mapper.predicateInput(Objects::nonNull)
```
Well that was easy, but the project evolves: the `lastName` can be null and should be upper-cased in your service. You don't question that bizarre requirement and add the new functionality:

```java
Mapper<PersonResource, Person> mapper = Datus.forTypes(PersonResource.class, Person.class)
    .mutable(Person::new)
    .from(PersonResource::getFirstName).into(Person::setFirstName)
    .from(PersonResource::getLastName)
         .given(Objects::nonNull, ln -> ln.toUpperCase()).orElseNull()
         .into(Person::setLastName)
    .build();

Mapper<PersonResource, Optional<Person>> notNullMapper = 
    mapper.predicateInput(Objects::nonNull)
```
Fancy! But you don't want any `Person` objects without a `lastName` to be valid, so let's add a predicate for the output object (of course you could already do it on the input object, but let me show some more features ;) ):

```java
Mapper<PersonResource, Person> mapper = Datus.forTypes(PersonResource.class, Person.class)
    .mutable(Person::new)
    .from(PersonResource::getFirstName).into(Person::setFirstName)
    .from(PersonResource::getLastName)
         .given(Objects::nonNull, ln -> ln.toUpperCase()).orElseNull()
         .into(Person::setLastName)
    .build();

Mapper<PersonResource, Optional<Person>> predicatedMapper = 
    mapper.predicate(Objects::nonNull, p -> p.getLastName() != null)
```

So what did we gain?
- declare your mappings, don't implement them
- no need for unit tests for such trivial nullabilty checks: they are declared and once the need for them arises simply added (this is an opinioated point)
- no more factory classes just one interface
- **datus even works for immutable destination objects (checkout the readme examples)**
- no reflection is used, everything is statically type checked

Oh and did I mention that `Mapper<A,B>` also provides `List<B> convert(List<A> input)` and `Map<A,B> convertToMap(List<A> input)` out of the box? Find out more in the [readme](https://github.com/roookeee/datus/blob/master/README.md) or the [extended user guide](https://github.com/roookeee/datus/blob/master/USAGE.md) that only takes about 15 minutes to read!

You like yourself a more implicit / automatic / annotation driven conversion process? Checkout alternatives to datus like [MapStruct](http://mapstruct.org/) or [ModelMapper](http://modelmapper.org/).

Thank you for your time, hit me up if you have any questions :)