Unit-API: What, How, and Why
Learn more about the complexity of using units and the best practices around them, as well as how to build a cloud-portable application.
Join the DZone community and get the full member experience.
Join For FreeThe now-famous units of measurement are the quantities that determine a substantial amount and that serve as a standard for possible comparisons, and that serves as a standard for other ratios. And just like in the real world, these elements are also used in the world of information technology. However, there several questions: What is the best way to use these standards in software? Are there good practices? What are the impacts of errors? The purpose of this article is to talk a little about the unit of measurement specification in Java.
Many software uses these resources in several points, for example, the weight of a product purchased in e-commerce. However, several questions arise; for example, what is the best way to represent the measured units? After all, why not just use just one numeric type and leave it implicit for your kind? It is impossible to make mistakes, correct? To talk a little about this, we’ll talk about just three examples here:
NASA had an initiative called "Star Wars" in 1983. The Strategic Defense Initiative (SDI) was a proposed missile defense system to protect the United States from attack by strategic ballistic nuclear weapons. The crew of the space shuttle was supposed to position it so that a mirror mounted on its side could reflect a laser beamed from the top of a mountain at 1,023 meters above sea level. The experiment failed because the computer program that controlled the movements of the bus interpreted the information received at the laser site as indicating the elevation in nautical miles instead of feet. As a result, the program positioned the space shuttle to receive lightning from a non-existent mountain, 10,023 nautical miles above sea level. From "The Development of Ballistic Missile Defense Software," by H. Lin, Scientific American, vol. 253, n. 6 (December 1985), p. 51.
NASA's Mars Climate Orbiter crashed in September 1999 because of a "silly mistake": wrong units in a program.
Gimli Glider: Air Canada Flight 143 was a Canadian domestic passenger flight between Montreal and Edmonton that ran out of fuel on July 23, 1983, at an altitude of 41,000 feet, midway through the trip. The reason is another "silly error" of a unit of measures.
For this article not to be a “silly error” software horror story, we will stop here. However, it is worth mentioning that these “silly mistakes” can generate disasters in your business, financial loss, besides that they can cause lives. And the big point is how to use and represent these units of measure in software.
To demonstrate this use, we will create a straightforward travel system, to do something directly and not get out of focus. The article will be a REST application that will manage a trip, with a set of flights, and that trip will have three attributes: the city of origin, city of destination, and the distance.
Here comes the first doubt in modeling, which is the way to represent distance.
xxxxxxxxxx
public class Travel {
private String to;
private String from;
private ? distance;
}
The first option will be to represent the type as just a numerical value; for our case, we will try long
.
xxxxxxxxxx
public class Travel {
private String to;
private String from;
private long distance;
}
As already mentioned, this approach generates several problems. How do we know which unit of measure the software is using? It could be in meters, kilometers, miles, etc.
A good option, indeed the only international measurement system, is using meters. However, it is not yet explicit, and we will do this by placing the unit of measurement as a suffix.
xxxxxxxxxx
public class Travel {
private String to;
private String from;
private long distanceMeters;
}
Leaving the unit of measure as part of the variable makes it explicit which unit of measure the software is using. However, there is no guarantee that it will be added or converted correctly to your code.
Unit-API
In situations where the type is more complex, there is the classic and famous “When Making a Type” to Martin Followers. A significant advantage of creating the kind is the guarantee and security in the use of its API by the user; that is, we are following the strategy of having a fail-safe API. Previously, I had already written an article talking about the advantages of using an API to type money (I also wrote a very cool book entitled by the community on the same topic). Following the same line of reasoning, we will use the Java specification to work with unit-API measurements.
In the case of this article, a Maven project will be used, so the first step to perform with Unit-API is simply to add the dependency within pom.xml
.
xxxxxxxxxx
<dependency>
<groupId>tech.units</groupId>
<artifactId>indriya</artifactId>
<version>2.0.2</version>
</dependency>
There is an interesting post that talks about some of the essential resources that exist within the Unit-API. In summary, this API will guarantee your security and stability to handle and manipulate units of measurement without worrying about it since there will be a component that will do that job for you.
xxxxxxxxxx
import tech.units.indriya.quantity.Quantities;
import javax.measure.MetricPrefix;
import javax.measure.Quantity;
import javax.measure.quantity.Length;
import static tech.units.indriya.unit.Units.METRE;
public class App {
public static void main(String[] args) {
Quantity<Length> distance = Quantities.getQuantity(1_000, METRE);
Quantity<Length> distanceB = Quantities.getQuantity(54_000, METRE);
final Quantity<Length> result = distance.add(distanceB);
final Quantity<Length> kiloDistance = result.to(MetricPrefix.KILO(METRE));
System.out.println(result);
System.out.println(kiloDistance);
}
}
Thus, our distance attribute will be represented by the "Quantity" type of specification. One crucial thing to remember: MongoDB does not support storing this type, so it is necessary that we also create a conversion to save the class in some way. To make it easier, we will store it as "String." Jakarta NoSQL has annotations very similar to JPA since some annotations are agnostic to the persistence of the database.
xxxxxxxxxx
import jakarta.nosql.mapping.Column;
import jakarta.nosql.mapping.Entity;
import jakarta.nosql.mapping.Id;
import java.time.LocalDate;
import java.util.List;
public class Trip {
private String trip;
private List<String> friends;
private LocalDate start;
private LocalDate end;
private List<Travel> travels;
}
import jakarta.nosql.mapping.Column;
import jakarta.nosql.mapping.Convert;
import jakarta.nosql.mapping.Entity;
import javax.measure.Quantity;
import javax.measure.quantity.Length;
public class Travel {
private String to;
private String from;
LengthConverter.class) (
private Quantity<Length> distance;
}
import jakarta.nosql.mapping.AttributeConverter;
import javax.measure.Quantity;
import javax.measure.format.QuantityFormat;
import javax.measure.quantity.Length;
import javax.measure.spi.ServiceProvider;
public class LengthConverter implements AttributeConverter<Quantity<Length>, String> {
private static final QuantityFormat FORMAT = ServiceProvider.current().getFormatService().getQuantityFormat();
public String convertToDatabaseColumn(Quantity<Length> attribute) {
if (attribute == null) {
return null;
}
return FORMAT.format(attribute);
}
public Quantity<Length> convertToEntityAttribute(String dbData) {
if (dbData == null) {
return null;
}
return (Quantity<Length>) FORMAT.parse(dbData);
}
}
In this article, we will use the DTO concept, and a DTO will be created between the entity and the DTO. To find out more details about the DTO trade-off, there is an article that talks about it in a very particular way. The critical point is that I can place some validations with my DTO using Bean Validation.
xxxxxxxxxx
import javax.validation.constraints.NotBlank;
import java.util.List;
public class TripDTO {
private String trip;
private List<String> friends;
private String start;
private String end;
private List<TravelDTO> travels;
private int totalDays;
private QuantityDTO distance;
}
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class TravelDTO {
private String to;
private String from;
private QuantityDTO distance;
}
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class QuantityDTO {
private String unit;
private Number value;
}
The last step of the application is to make resources available thanks to the TripResource
class. We have the mapper that does the conversion and isolation of the entity in addition to the integration with the database thanks to TripRepository
.
xxxxxxxxxx
import jakarta.nosql.mapping.Repository;
import javax.enterprise.context.ApplicationScoped;
import java.util.stream.Stream;
public interface TripRepository extends Repository<Trip, String> {
Stream<Trip> findAll();
}
import jakarta.nosql.mapping.Repository;
import javax.enterprise.context.ApplicationScoped;
import java.util.stream.Stream;
public interface TripRepository extends Repository<Trip, String> {
Stream<Trip> findAll();
}
import org.modelmapper.ModelMapper;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.stream.Collectors;
"trips") (
MediaType.APPLICATION_JSON) (
MediaType.APPLICATION_JSON) (
public class TripResource {
private TripRepository repository;
private ModelMapper mapper;
public List<TripDTO> findAll() {
return repository.findAll()
.map(t -> mapper.map(t, TripDTO.class))
.collect(Collectors.toList());
}
"{id}") (
public TripDTO findById( ("id") String id) {
return repository.findById(id)
.map(d -> mapper.map(d, TripDTO.class))
.orElseThrow(
() -> new WebApplicationException(Response.Status.NOT_FOUND));
}
public TripDTO insert( TripDTO tripDTO) {
final Trip trip = mapper.map(tripDTO, Trip.class);
return mapper.map(repository.save(trip), TripDTO.class);
}
"{id}") (
public void deleteById( ("id") String id) {
repository.deleteById(id);
}
}
Within the entity we have two methods for reading: one to return the total distance and the total of days used on that trip.
xxxxxxxxxx
public class Trip {
// more code ....
public Quantity<Length> getDistance() {
return getTravels()
.stream()
.map(Travel::getDistance)
.reduce((a, b) -> a.add(b))
.orElse(Quantities.getQuantity(0, Units.METRE));
}
public long getTotalDays() {
return ChronoUnit.DAYS.between(start, end);
}
}
And it is possible to create units of measures based on the distance SI, in this case, the mile:
xxxxxxxxxx
Unit<Length> mile = Units.METRE.multiply(1609.344).asType(Length.class);
And it is possible to use and handle it safely without the small errors, and significant disasters mentioned at the beginning of the article.
Once the application is ready, the next step is to upload the app and perform the tests.
xxxxxxxxxx
mvn clean package kumuluzee:repackage
java -jar target/microprofile.jar
The application is up and running, so the next step is to enter data and check the result:
xxxxxxxxxx
curl --location --request POST 'http://localhost:8080/trips' \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{"trip": "euro-trip", "friends": ["Otavio", "Edson", "Bruno"], "start": "2010-03-01", "end": "2010-04-01", "travels": [
{"to": "London", "from": "São Paulo", "distance": {"unit": "km", "value": 9496.92}}, {"to": "London", "from": "Paris", "distance": {"unit": "km", "value": 342.74}},
{"to": "Paris", "from": "Rome", "distance": {"unit": "km", "value": 1106.27}}]}'
curl 'http://localhost:8080/trips'
Move to The Cloud
Recently the term cloud-native has become quite popular and much-discussed, describing a set of best practices for optimizing an application in the cloud through the container, orchestration, and automation.
A straightforward way to take your application cloud-native is through Platform.sh. In general, it is a platform as a service, PaaS, which is committed to the concept of infrastructure as code.As mentioned in this article, at least three files are required to perform the deployment: One for application, for services, and another for routes.
xxxxxxxxxx
mongodb
type mongodb3.6
disk1024
In this article, we talked a little about the unit of measures, the motivation to use good practices like using type when the variable requires a high degree of complexity like the units of standards, in addition to creating an application that is easily portable to the cloud. Thanks to Java technologies that are mature and are increasingly prepared for the world of cloud-native.
Opinions expressed by DZone contributors are their own.
Comments