Problem
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) {
}
}
}
}
Solution
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 ;-))