Tiny Types

Because yes, we hate string-oriented programming.

WHERE ARE WE?

  • statically typed
  • object-oriented (or at least w/ OO-support)

(Language)

Let's say, Java.

WHERE ARE WE?

  • business logic
  • domain model representation

(Program context)

Actually can be any program portion (generic libraries, middleware...), but it's usually more useful in these contexts.

WHAT ARE TTYPES?

  • class wrappers for native types
  • just a value holder class
  • w/ equality, hashing
  • immutable*

* : or at least with the same mutability constraints of the wrapped type in the used language

LIKE THIS

public class DeviceId {
    
    public final long value;

    public DeviceId(long value){
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof DeviceId == false) {
            return false;
        }
        final DeviceId other = (DeviceId) rhs;
        return this.value == other.value;
    }

    @Override
    public int hashCode() {
        return Long.hashCode(value);
    }

}

THIS HAPPENED


public doSomethingWithIps(SourceDevice s, TargetDevice t)
    final long timestamp = now();
    final long sourceIpv4Address = s.getAddress();
    final long sourceIpv4Mask = s.getNetmask();
    final long targetIpv4Address = t.getAddress();
    final long targetIpv4Mask = t.getNetmask();
    // snip...
    final long sourceNetwork = timestamp & sourceIpv4Mask;
    return something;
}
    

Can you spot the error?

AND SO?

Just test it, right?

CAN DO BETTER

  • compiler is your friend
  • developer tools like types

RUNTIME COMPILE ERRORS

Plus, need less tests.

public List<LogEntry> loadSomeLogEntriesForDevice(long deviceId, long howMany) {
    ....
}

final long deviceId = 9999;
final long pageSize = 20;
return loadSomeLogEntriesForDevice(pageSize, deviceId); //woooops!
public List<LogEntry> loadSomeLogEntriesForDevice(DeviceId deviceId, long howMany) {
    ....
}

final DeviceId deviceId = new DeviceId(9999);
final long pageSize = 20;
return loadSomeLogEntriesForDevice(pageSize, deviceId); //woooops!

this can exist in live system:

this can not (doesn't compile):

SIGNATURES WITH MEANING

Good naming helps even without typing, but it's harder to get wrong here.

BiFunction<Long, Long, Long> ...

BiFunction<Long, Long, Long> ...

BiFunction<Long, Long, Long> ...
BiFunction<Long, Long, Long> ...

BiFunction<DeviceId, Timestamp, ConfigurationId> ...

BiFunction<Latitude, Longitude, Altitude> ...

name the bifunction:

give it a shot now:

getDeviceConfigurationAt

getAltitudeAtLocation

sum
getDeviceConfigurationAt

getAltitudeAtLocation

sum

LESS HEADACHES IN  SPRING

@Bean
public BiFunction<Long, Long, Long> getDeviceConfigurationAt(){ ... }

@Bean
public BiFunction<Long, Long, Long> getAltitudeAtLocation(){ ... }

@Bean
public BiFunction<Long, Long, Long> sum(){ ... }

@Bean
public SomethingUsingABiFun something(BiFunction<Long, Long, Long> bf) { ... }

Babum

LESS HEADACHES IN  SPRING

@Bean
@Qualifier("this one")
public BiFunction<Long, Long, Long> getDeviceConfigurationAt(){ ... }

@Bean
@Qualifier("not this")
public BiFunction<Long, Long, Long> getAltitudeAtLocation(){ ... }

@Bean
@Qualifier("neither this")
public BiFunction<Long, Long, Long> sum(){ ... }

@Bean
public SomethingUsingABiFun something(
                                 @Qualifier("this one") BiFunction<Long, Long, Long> bf
                            ) { ... }

OK

LESS HEADACHES IN  SPRING

@Bean
public BiFunction<DeviceId, Timestamp, ConfigurationId> getDeviceConfigurationAt(){ ... }

@Bean
public BiFunction<Latitude, Longitude, Altitude> getAltitudeAtLocation(){ ... }

@Bean
public BiFunction<Long, Long, Long> sum(){ ... }

@Bean
public SomethingUsingABiFun something(
                                BiFunction<DeviceId, Timestamp, ConfigurationId> bf
                            ) { ... }

Better

LESS HEADACHES IN  SPRING

@Bean
public ConfigurationHistory getDeviceConfigurationAt(){ ... }

@Bean
public TopographicMap getAltitudeAtLocation(){ ... }

@Bean
public BiFunction<Long, Long, Long> sum(){ ... }

@Bean
public SomethingUsingAConfigHistory something(ConfigurationHistory bf) { ... }

Betterest - but this goes a little beyond just wrapping base types.

ENFORCE  SEMANTICS 

public class Tag {
    public final String value;
    
    public Tag(String value){
        // a tag cannot contain spaces, that would be two tags!
        if(value.contains(" ")){
            throw new IllegalArgumentException("invalid");
        }
        this.value = value;
    }
}
public class PersonName {
    public final String value;
    
    @Override
    public boolean equals(Object o){
        // snip null & type checking...
        // Doesn't matter if it's written MaRiO or mario,
        // we're still talking about Mario!
        return value.toLowerCase().equals(other.toLowerCase());
    }
}

BEANFORE 

public class Device {
    
    public long getId(){ ... }
    public long getFirstSeen(){ ... }
    public long getLastSeen(){ ... }
    public long getNumberOfMeasures(){ ... }
    public String getSerialNumber(){ ... }
    public String getConfigurationText(){ ... }

}

BEANS  AFTER 

public class Device {
    
    public DeviceId getId(){ ... }
    public Timestamp getFirstSeen(){ ... }
    public Timestamp getLastSeen(){ ... }
    public long getNumberOfMeasures(){ ... }
    public DeviceSerial getSerialNumber(){ ... }
    public String getConfigurationText(){ ... }

}

ALL FOR FREE?

CLASS PROLIFERATION

public class DeviceId { ... }

public class ConfigurationId { ... }

public class Tag { ... }

public class Timestamp { ... }

public class Latitude { ... }

public class Longitude { ... }

public class Altitude { ... }

public class DeviceSerial { ... }

public class PersonName { ... }

WHAT ABOUT... HIBERNATE

public class DeviceId implements EnhancedUserType {

    @Override
    public abstract int[] sqlTypes(){ ... }

    @Override
    public abstract Class returnedClass(){ ... }

    @Override
    public String objectToSQLString(Object value){ ... }

    @Override
    public Object nullSafeGet(...) 
        throws HibernateException, SQLException { ... }

    @Override
    public void nullSafeSet(...) 
        throws HibernateException, SQLException { ... }

    ...

}

WHAT ABOUT... JACKSON

public class DeviceIdSerializer extends JsonSerializer<DeviceId> {

    @Override
    public void serialize(...)
        throws IOException, JsonProcessingException {...}
}

public class DeviceIdKeySerializer extends JsonSerializer<DeviceId> {

    @Override
    public void serialize(...)
        throws IOException, JsonProcessingException {...}
}

public class DeviceIdDeserializer extends JsonDeserializer<DeviceId> {

    @Override
    public Object deserialize(...)
        throws IOException, JsonProcessingException {...}
}

// + the module

WHAT ABOUT... SPRING WEB

public class StringToDeviceIdConverter implements Converter<String, DeviceId> {

    @Override
    public DeviceId convert(String source) {...}
}

WHAT ABOUT... 

DOZER

XSTREAM

ANDROID PARCHELABLE

WHATEVER

TINY RECAP:

MEANING

ERRORS 

NOT FOR FREE 

SUPPORT LIBRARY MAY HELP

TESTS NEEDED 

THANKS

TinyTypes

By Tsukihara Caligin

TinyTypes

What they are and why use them.

  • 2,642