Aerospike java object mapper using configuration file approach to read CDT(json) data

I have below container class which holds multiple information about Car stored in aerospike bin. Example:

Class Car {
   String id;
   BasicInformation information;
   PartInformation partInfo;
   CompanyInformation companyInfo;
   Specification specification;
   ModelInformation modelInfo;
}

Above class is stored in Car set and each object of Car class is stored in bins and these objects are nested in structure. This bin will have complex json data. Because I don’t have control over Car and Object classes, I am thinking about using external configuration approach present in aerospike java object mapper library → GitHub - aerospike/java-object-mapper: The Java Object Mapper is a simple, light-weight framework used to map POJOs to the Aerospike database. Using simple annotations or a configuration YAML file to describe how to map the data to Aerospike, the project takes the tedium out of mapping the data through the powerful, low level interface.. Currently I’m getting Unsupported type error while retrieving the object data. There are very few example implementation currently available to refer for external file based. Can someone help me to fix this issue?

Hi Darshan,

Thanks for reaching out. You didn’t provide a lot of details about what sort of errors you’re seeing, so I created an example which I hope may be illustrative.

I have created a Car class which contains a BasicInformation instance and a PartInformation instance. The BasicInformation is simply a class with fields and no other objects. The PartInformation however has 2 lists of items, PartDetail and CustomPart. I was thinking that PartDetail might be standard parts and hence be referenced to parts in another set, but the CustomPart would be something specific to that Car so would probably be embedded.

This allows you to see different sort of approaches. I’ve put all my Embed objects as MAPs to make reading easier, but you can obviously change this to lists as necessary.

There are 2 ways of doing external configuration, using code or YAML files. The YAML file can be specified either as an external file or as a String. I’ve taken the latter approach to keep everything in the same file.

This sample code shows both YAML and code approaches. Both produce the same result, but the code one did expose an issue which I’ve fixed in code and will check in shortly. So if you need to use this now, I would recommend the YAML approach.

Please let me know if you have any further questions. Note that I’m using Lombok to minimize the amount of code, this is definitely not a requirement.

Output in AQL looks like:

aql> select * from test.car
*************************** 1. row ***************************
id: "1010-2324-23231"
information: MAP('{"make":"Toyota", "model":"Corolla", "year":1985}')
partInfo: MAP('{"customParts":{101:{"description":"Lime green custom spoiler with wings", "id":101, "name":"spoiler"}, 102:{"description":"See any Bond movie", "id":102, "name":"rocket lancher"}, 103:{"description":"Great way to deal with those annoying passengers", "id":103, "name":"ejector seats"}}, "parts":[1, 2, 3]}')

1 row in set (0.019 secs)

Note that I’m using RAW mode here as JSON output will hide the non-String keys of the CustomParts, making it look like they’re not saved in the database when they clearly are.

package com.timf;

import java.util.Arrays;
import java.util.List;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.IAerospikeClient;
import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
import com.aerospike.mapper.annotations.AerospikeReference.ReferenceType;
import com.aerospike.mapper.tools.AeroMapper;
import com.aerospike.mapper.tools.configuration.ClassConfig;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

public class DiscussQuestion20240605 {
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class BasicInformation {
        private String make;
        private String model;
        private int year;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class PartDetail {
        private long id;
        private String name;
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class CustomPart {
        long id;
        private String name;
        private String description;
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PartInformation {
        private List<PartDetail> parts;
        private List<CustomPart> customParts;
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Car {
        private String id;
        private BasicInformation information;
        private PartInformation partInfo;
     }
    
    public static void main(String[] args) throws Exception {
        IAerospikeClient client = new AerospikeClient("localhost", 3100);
        
        // The configuration files are for code-based config
        ClassConfig carConfig = new ClassConfig.Builder(Car.class)
                .withKeyField("id")
                .withNamespace("test")
                .withSet("car")
                .withFieldNamed("information").beingEmbeddedAs(EmbedType.MAP)
                .withFieldNamed("partInfo").beingEmbeddedAs(EmbedType.MAP)
                .build();
        
        ClassConfig basicInfoConfig = new ClassConfig.Builder(BasicInformation.class)
                .build();
        
        ClassConfig partInfoConfig = new ClassConfig.Builder(PartInformation.class)
                .withFieldNamed("customParts").beingEmbeddedAs(EmbedType.MAP, EmbedType.MAP)
//                .withFieldNamed("parts").
                .withFieldNamed("parts").beingReferencedBy(ReferenceType.ID)
                .build();
        
        ClassConfig partDetailConfig = new ClassConfig.Builder(PartDetail.class)
                .withKeyField("id")
                .withNamespace("test")
                .withSet("partDetail")
                .build();
        
        ClassConfig customPartConfig = new ClassConfig.Builder(CustomPart.class)
                .withKeyField("id")
                .build();
        
        // This is for YAML based config
        String configYaml = """
                classes:
                - class: com.timf.DiscussQuestion20240605$Car
                  namespace: test
                  set: car
                  key:
                    field: id
                  bins:
                    - field: information
                      embed:
                        type: MAP
                    - field: partInfo
                      embed:
                        type: MAP
                - class: com.timf.DiscussQuestion20240605$BasicInformation
                - class: com.timf.DiscussQuestion20240605$PartDetail
                  key:
                    field: id
                  namespace: test
                  set: partDetail
                - class: com.timf.DiscussQuestion20240605$CustomPart
                  key:
                    field: id
                - class: com.timf.DiscussQuestion20240605$PartInformation
                  bins:
                  - field: customParts
                    embed:
                      type: MAP
                      elementType: MAP
                  - field: parts
                    reference:
                      type: ID
                      lazy: false
                      batchLoad: true
                """;
        
        // Uncomment this for code-based config
//        AeroMapper mapper = new AeroMapper.Builder(client)
//                .withClassConfigurations(carConfig, 
//                        basicInfoConfig,
//                        partInfoConfig,
//                        partDetailConfig,
//                        customPartConfig
//                    )
//                .build();
        
        // Uncomment this for YAML based config
        AeroMapper mapper = new AeroMapper.Builder(client)
                .withConfiguration(configYaml)
                .build();
        
        PartDetail part1 = new PartDetail(1, "wheels");
        PartDetail part2 = new PartDetail(2, "dashboard");
        PartDetail part3 = new PartDetail(3, "radiator");
        
        CustomPart custPart1 = new CustomPart(101, "spoiler", "Lime green custom spoiler with wings");
        CustomPart custPart2 = new CustomPart(102, "rocket lancher", "See any Bond movie");
        CustomPart custPart3 = new CustomPart(103, "ejector seats", "Great way to deal with those annoying passengers");
        
        PartInformation partInformation = new PartInformation(
                Arrays.asList(part1, part2, part3),
                Arrays.asList(custPart1, custPart2, custPart3)
            );
        
        BasicInformation basicInfo = new BasicInformation("Toyota", "Corolla", 1985);
        Car car = new Car("1010-2324-23231", basicInfo, partInformation);
        
        mapper.save(part1, part2, part3);
        mapper.save(car);
        
        Car readCar = mapper.read(Car.class, car.getId());
        System.out.println("Read: " + readCar);
    }
}