0
\$\begingroup\$

Need to parse MongoDB collection and save parsed data into different postgres tables. MongoDB stores documents with different type field, for each type need to write handler to parse the document of this type.

I wrote Service class but it is overcomplicated. I see this troubles:

  • Bad decomposition
  • A lot of DAO classes in one class
  • Function that initializes handlers is too long

How to simplify this code and improve its structure?

package services;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Updates;
import mongodb.MongoDB;
import org.bson.Document;
import static com.mongodb.client.model.Filters.ne;

import java.text.ParseException;
import java.util.HashMap;

import org.bson.conversions.Bson;
import pg.dao.*;
import pg.InboxReader;

public class Service {

    private MongoClient client;
    private HashMap<String, InvoiceWorker> handlers;
    private StationDAO stations;
    private VpuListDAO vpu;
    private CarUpRequestsDAO carUpRequestsDAO;
    private CarUpResponseDAO carUpResponseDAO;
    private CreatorIdDAO creatorIdDAO;
    private FileSyncDAO fileSyncDAO;
    private InboxReader inboxReader;
    private MongoCollection<Document> inbox;
    private MongoCollection<Document> inboxParsed;

    public Service() {
        this.client = MongoDB.getInstance();
        this.handlers = new HashMap<>();
        this.stations = new StationDAO();
        this.inboxReader = new InboxReader();
        this.vpu = new VpuListDAO();
        this.carUpRequestsDAO = new CarUpRequestsDAO();
        this.carUpResponseDAO = new CarUpResponseDAO();
        this.creatorIdDAO = new CreatorIdDAO();
        this.fileSyncDAO = new FileSyncDAO();

        initHandlers();
    }

    private void initHandlers() {
        handlers.put("file", (Document doc) -> {
            final String creatorId = doc.getString("creatorId");
            final String sysId = doc.getString("sysId");

            if (fileSyncDAO.checkExists((String)doc.get("creatorId"), (String)doc.get("sysId"))) {
                System.out.println("The entry already exists in postgres");
                return;
            }

            fileSyncDAO.saveFileSync((String)doc.get("creatorId"),
                    (String)doc.get("name"),
                    (String)doc.get("fileHash"),
                    (String)doc.get("sysId"),
                    (Long)doc.get("dateCreated"));
            System.out.println("Sync document");
            System.out.println(doc.toJson());

        });

        handlers.put("etranInvoice", (Document doc) -> {
            inboxReader.readDocument(doc);
            System.out.println("Run etranInvoice handler for doc: " + doc.toJson());
        });

        handlers.put("etranVPU", (Document doc) -> {
            if (doc.containsKey("carNumber") && doc.containsKey("invNumber")) {
                final String carNumber = doc.getString("carNumber");
                final String invNumber = doc.getString("invNumber");
                final String vpuDate = doc.getString("vpuDate");

                System.out.println(doc.toJson());

                if (!vpu.exists(carNumber, invNumber)) {
                    insertVpu(carNumber, invNumber, vpuDate);
                }else{
                    updateVpuDates(carNumber, invNumber, vpuDate);
                }
            }
        });

        handlers.put("carUpRequest", (Document doc) -> {
            final String creatorId = doc.getString("creatorId");
            if (creatorIdDAO.getCreatorId().equalsIgnoreCase(creatorId)) return;

            final String carNumber = doc.getString("carNumber");
            final String invNumber = doc.getString("invNumber");
            if (!carUpRequestsDAO.wagonIdentifierExists(carNumber, invNumber)) {
                carUpRequestsDAO.insertDocument(doc);
            }
        });

        handlers.put("carUpResponse", (Document doc) -> {
            final String creatorId = doc.getString("creatorId");
            if (creatorIdDAO.getCreatorId().equalsIgnoreCase(creatorId)) return;

            final String carNumber = doc.getString("carNumber");
            final String invNumber = doc.getString("invNumber");
            if (!carUpResponseDAO.wagonIdentifierExists(carNumber, invNumber)) {
                carUpResponseDAO.insertDocument(doc);
            }
        });
    }

    private void insertVpu(String carNumber, String invNumber, String vpuDate) {
        try {
            long vpuDateTimestamp = inboxReader.formatDate(vpuDate);
            vpu.insertVpuDate1(carNumber, invNumber, vpuDateTimestamp);
        }
        catch (ParseException e) {
            System.out.println(e);
        }
    }

    private void updateVpuDates(String carNumber, String invNumber, String vpuDate) {
        try {
            long vpuDateTimestamp = inboxReader.formatDate(vpuDate);
            long date2 = vpu.selectVpuDate1(carNumber, invNumber);

            if(date2 < vpuDateTimestamp){
                vpu.setDate2(carNumber, invNumber, vpuDateTimestamp);
            }else{
                vpu.setDate1(carNumber, invNumber, vpuDateTimestamp);
            }
        }
        catch (ParseException e) {
            System.out.println(e);
        }
    }

    public synchronized void service() {
        inbox = client.getDatabase("test")
                .getCollection("inbox");
        inboxParsed = client.getDatabase("test")
                .getCollection("inbox_parsed");
        MongoCursor<Document> cursor = inbox
                .find(ne("parsed", "true"))
                .iterator();

        parseInbox(cursor);
    }

    private void parseInbox(MongoCursor<Document> cursor) {
        try {
            while (cursor.hasNext()) {
                Document doc = cursor.next();
                if (doc.containsKey("type")) {
                    String type = doc.getString("type");
                    if (handlers.containsKey(type)) {
                        handlers.get(type).proceed(doc);
                    }
                }
                setDocumentParsed(doc);
                //moveToInboxParsed(doc);
            }
        }
        finally {
            cursor.close();
            System.out.println("Run service()");
        }
    }

    private void setDocumentParsed(Document doc) {
        Bson updates = Updates.set("parsed", "true");
        inbox.findOneAndUpdate(doc, updates);
    }

    private void moveToInboxParsed(Document doc) {
        inboxParsed.insertOne(doc);
        inbox.deleteOne(doc);
    }
}

Main.java:

import services.Service;

public class Main {
    public static void main(String[] args) {
        Service s = new Service();
        while (true) {
            s.service();
            try {
                Thread.sleep(5000);
            }
            catch (InterruptedException e) {

            }
        }
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Stating the obvious: your handlers all have a common interface (basically the same as Consumer<Document>) and implement their respective business logic. Thus, create a common interface and implement the different handlers in their specific classes, e.g.

public interface DocumentHandler {
    public void handle(Document doc);
}

public class FileHandler implements DocumentHandler {
    private FileSyncDAO fileSyncDao;

    public FileHandler() {
        this.fileSyncDao = new FileSyncDao();
        // or alternatively, pass this in via constructor to honor
        // tell-don't ask principle
    }

    public void handle(Document doc) {
        // code from your lambda
    }
}

Repeat for each handler, and init you map with

handlers.put("file", new FileHandler());
...

From there, you might check extensions regarding metadata, e.g. adding a String getHandledType() method to the handler, so that you don't have to "know" the key from the outside.

If you are in for the real next step in programming paradigms, you might check the possibility to use some kind of auto-registration, e.g. by running it in a CDI container and using an Instance<DocumentHandler> to create the map. (If you go that path, prepare for some steep learning curves and big rewards at the end of the way ;-))

\$\endgroup\$
2
  • \$\begingroup\$ Can you please show how to make auto-registration? I heard about DI and aspects, but I dont know how to use this properly. \$\endgroup\$
    – typemoon
    Jul 27, 2018 at 11:41
  • \$\begingroup\$ After you have the complete thing running in a CDI container, just @Inject Instance<DocumentHandler> handlers and then just stream over the handlers in the instance's stream(). However, to get started be sure to first go through a beginner's tutorial on CDI. \$\endgroup\$
    – mtj
    Jul 27, 2018 at 12:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.