Skip to content
This repository has been archived by the owner on Dec 16, 2022. It is now read-only.
/ valueclasses Public archive

Base abstractions and templates for value classes for Java

License

Notifications You must be signed in to change notification settings

tguzik/valueclasses

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

valueclasses

Build Status Java CI with Maven

This small library aims to provide a blueprint for value-based classes on JVM before Project Valhalla becomes widely available.

These blueprints are designed to hold a single, usually primitive, value while at the same time giving them distinct type information and thus leveraging Java type system. The aim is to help you create your complex applications by spending less time on frustrating tasks fixing bugs that are the result of using wrong variable in function call, which is often the case in Stringly-typed frameworks (we've all seen these).

For example, let's say that you have an use case where you have to update account balance for a customer, while saving Point Of Sale identifier and an optional comment:

public void updateAccountBalance( Long customerId, 
                                  Long pointOfSaleId, 
                                  Long delta, 
                                  @Nullable String comment ) {
    // [...]
}

Now, somewhere in the middle of your business logic you do something that boils down to:

public void processTransaction( TransactionContext context, Customer customer ) {
    // [...]
    updateAccountBalance( customer.getId(),
                          context.getId(),
                          context.getPriceInCents(),
                          context.getTransactionTitle() );
    // [...]
}

Now, this is fine and dandy, but what happens when the method to update the balance changes its signature without changing all invocations? How soon would you know that something isn't right? Of course, this should come up in unit tests, but that depends on their quality.

Instead of relying on something that might be there and might find the mistake, the idea is to enforce correctness at compilation stage. This way most of the mistakes will be weeded out by the time the tests are ran.

Side note: Check out errorprone too - it tries to achieve similar goal by being performing the static analysis at compile time. Findbugs and PMD are helpful as well. All three have Maven plugins for easy integration into your build.

Let's take a look at this method signature:

public void updateAccountBalance( CustomerId customerId,
                                  PointOfSaleId pointOfSaleId,
                                  PriceInCents delta,
                                  @Nullable String comment ) {
    // [...]
}

...and tell me, how hard you would have to try to make an error in here to pass as an honest mistake? :-)

Of course, you should use these value-based objects where it is reasonable. Data model classes, complicated APIs and business logic are a good places for them. On the other hand tight loops in graphics processing are not.

JDK14 Records

The JDK Record feature is proposed to target JDK14: https://openjdk.java.net/jeps/359

This feature would replace most of what this library is providing with JDK built-in stuff. If you can migrate when JDK14 is released, you should migrate. If your project cannot migrate or you are stuck on a lower JDK version, you can keep on using this library.

I don't plan on archiving/abandoning/removing it anytime soon (hey, i use it myself ;) ), but at the same time there's just not that much one can innovate in this space without making breaking changes, and I don't want to make breaking changes. I don't want to bump the JDK requirement either, because I know people are still using JDK 1.8 and below.

How do I get it?

The library is available in Maven Central repository. You can use it in your projects via this dependency:

<dependency>
    <groupId>com.tguzik</groupId>
    <artifactId>valueclasses</artifactId>
    <version>1.0.2</version>
</dependency>

So what's inside?

See Javadoc or the demo code below for more information.

Short demo

Here are some examples of different types of value-based classes you can create with this library:

import com.tguzik.value.adapters.JaxbStringValueAdapter;
import com.tguzik.value.StringValue;
// [...]

@Immutable
@XmlJavaTypeAdapter( value = FirstName.Adapter.class )
public final class FirstName extends StringValue
{
    private FirstName( String value ) {
        super( value );
    }

    public static FirstName valueOf( String firstName ) {
        return new FirstName( StringUtils.trimToEmpty( firstName ) );
    }

    public static class Adapter extends JaxbStringValueAdapter<FirstName>
    {
        @Override
        protected FirstName createNewInstance( String value ) {
            return FirstName.valueOf( value );
        }
    }
}
import com.tguzik.value.adapters.JaxbValueAdapter;
import com.tguzik.value.Value;
// [...]

@Immutable
@XmlJavaTypeAdapter( value = CustomerId.Adapter.class )
public final class CustomerId extends Value<Long>
{
    private CustomerId( Long value ) {
        super( value );
    }

    public static CustomerId valueOf( Long value ) {
        /* You can plug additional validation (recommended) or an instance cache here, if you need one. */
        return new CustomerId( value );
    }

    /** For the purpose of this example let's assume you use JaxB-compatible library */
    public static class Adapter extends JaxbValueAdapter<Long, CustomerId>
    {
        @Override
        protected ClientId createNewInstance( Long value ) {
            return ClientId.valueOf( value );
        }
    }
}
public final class LastName extends StringValue { [...] }
public final class EmailAddress extends StringValue { [...] }

Now, assuming that you didn't go out of your way to create these value-based objects mutable (which you shouldn't), you can create this class to hold the data about customer:

/*
 * Reflection-based .hashCode(), .equals() and .toString() are already defined in 
 * the com.tguzik.objects.BaseObject class.
 */
@Immutable
@ParametersAreNonnullByDefault
public final class Customer extends BaseObject {
    private final CustomerId customerId;
    private final FirstName firstName;
    private final LastName lastName;
    private final EmailAddress emailAddress;
    // ..and whatever else you need

    public Customer( CustomerId customerId, 
                     FirstName firstName, 
                     LastName lastName, 
                     EmailAddress emailAddress ) {
        this.customerId = Objects.requireNonNull( customerId );
        this.firstName = Objects.requireNonNull( firstName );
        this.lastName = Objects.requireNonNull( lastName );
        this.emailAddress = Objects.requireNonNull( emailAddress );
    }

    public CustomerId getCustomerId() {
        return customerId;
    }

    public FirstName getFirstName() {
        return firstName;
    }

    public LastName getLastName() {
        return lastName;
    }

    public EmailAddress getEmailAddress() {
        return emailAddress;
    }
}

Dependencies

Outside of unit test dependencies, this project requires following third party libraries:

  • org.apache.commons:commons-lang3
  • javax.xml.bind:jaxb-api
  • com.tguzik:annotatons (includes JSR 305 annotations)
  • JDK 1.7+

License

Source code in this repository is available under MIT License.

About

Base abstractions and templates for value classes for Java

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages