Search code examples
javamongodbcodecreactive-streams

How can I implement MongoDB Codecs for composite classes?


I'm writing a database access module for existing using MongoDB and its reactive streams Java Driver (in a Spring boot project). Some of the objects are stored in composite way in DB with only the reference ID stored and the object is then assembled when fetching from DB (see example code). I am using codecs for the Java object creation but I'm not sure how to make things in composite way.

The previous sync driver using solution simply parsed the documents returned from MongoDB field-by-field and it called the appropriate services when it needed to do a DB lookup for assembly. Since this solution was quickly getting completely unmanageable, I decided to use MongoDB codecs for document parsing directly into Java Objects. However, I'm not sure how I can do the same using Codecs as they do not seem to let me connect to the DB to fetch the appropriate data at the object creation and doing extra fetching and manual setting of those objects after creation seems inefficient and not really the correct way.

MongoDB Rules collection example:

{
    "_id": {
        "$oid": "5afa865b613640f83206e23e"
    },
    "id": "R2",
    "typeId": "RT1",
    "name": "example rule",
}

RuleType collection example:

{
    "_id": {
        "$oid": "5afa865c613640f83206e260"
    },
    "id": "RT1",
    "name": "Example rule type",
}

Rule.java

public Class Rule {
    private id;
    private name;
    private RuleType ruleType;

    // Constructors, setters, getters, etc.
}

RuleType.java

public Class Rule {
    private id;
    private name;

    // Constructors, setters, getters, etc.
}

RuleDbService.java

public class RuleDbService {
    private final MongoCollection<Rule> ruleCollection;
    private final ObservableSubscriber<Rule> subscriber = new ObservableSubscriber<>();

    public RuleDbService(final MongoDbAdapter mongoDbAdapter) {
        this.ruleCollection = mongoDbAdapter.getCollection("rules", Rule.class);
    }

    public Optional<Rule> findFirst() {
        this.ruleCollection.find().first().subscribe(this.subscriber);
        return Optional.of(this.subscriber.get())
    }

RulesCodec.java

public class RulesCodec implements Codec<Rule> {

    private final Codec<Document> documentCodec;

    public RulesCodec() {
        this.documentCodec = new DocumentCodec();
    }

    public RulesCodec(final Codec<Document> codec) {
        this.documentCodec = codec;
    }


    @Override
    public Rule decode(final BsonReader reader, final DecoderContext decoderContext) {
        final Document document = this.documentCodec.decode(reader, decoderContext);

        final Rule rule = new Rule();
        rule.setId(document.getString("id"));
        // rule.setType() <- How to do this? Can I call to some findById(id) function in RuleTypeDbService from here?
        rule.setName(document.getString("name"));

        return rule;
    }

    @Override
    public void encode(final BsonWriter writer, final Rule value, final EncoderContext encoderContext) {
        final Document document = new Document();

        final String ruleId = value.getId();
        final String name = value.getName();
        final String typeId = (value.getType() != null) ? value.getType().getId() : null;

        if (ruleId != null) {
            document.put("id", ruleId);
        }

        if (name != null) {
            document.put("name", name);
        }

        if (typeId != null) {
            document.put("typeId", typeId);
        }

        this.documentCodec.encode(writer, document, encoderContext);

    }

    @Override
    public Class<Rule> getEncoderClass() {
        return Rule.class;
    }
}

I would like the RuleDbService to be able to assemble the rule including the ruleType by looking it up in the database instead of returning it with empty field and me having to do manual external lookup and setting of the type after the codec parsing or some other workarounds. What would be the best way for this?

NOTE: I unfortunately cannot modify the database or model structure in any way, so I cannot use any ORM frameworks like Morphia that require the unique id field from MongoDb to be represented in the Java Model, so I have to decode the documents manually.


Solution

  • You might look in to using an aggregation with $lookup. https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/