1

I need to implement a class hierarchy and be able to implement it in multiple ways.
I want to create a logical representation of a query language, and then be able to parse it to/from different formats (XML, JSON, YAML, etc), and switch between them.

I tried to start with the classes below (Logical Hierarchy) to represent the language (I know there are already tools for SQL Queries but this is just the basic, it will expand), Then I created interface QueryComponentParser<T extends QueryNode, U> followed by parsers & deparsers classes hierarchies for every format I need (json parser hierarchy below for example)

I ended up duplicating the logical hierarchy twice for every format I needed (once parse, once deparse), resulting with 7 classes hierarchy just for 3 formats (JSON, XML and YAML) which are basically the same and very coupled to the main logical hierarchy. I considered inherit from the classes instead of creating parsers, but I still had the structural duplication, and it made the interchangeability between formats (parse from json and deparse to xml) harder.

What is the best design to achieve my goals?

EDIT: Clarification
My organization created its own query language, something between SQL and grahpQL. For now it is too late to change this standard. Therefore I'm trying to create the best implementation for the situation. The querying language actions are the base of the project, so IMO the best way is separation to 3 layers:
Query format -> logical query -> query implementation
The query format may change from xml to json, or even both. The query implementation may change according to the DB. The logical query should not be affected by changes to other parts of the system

Logical Hierarchy

public abstract class QueryNode {
}

public class QueryRoot extends QueryNode {
    protected final Select select;
    protected final Optional<Where> where;
}

public class Select extends QueryNode {
    protected final Set<EntityProjection> entities;
}

public class EntityProjection extends QueryNode {
    protected final String entityName;
    protected final Set<String> fields;
}

public  class Where extends QueryNode {
    protected final List<Expression> expressions;
}

public abstract class Expression extends QueryNode {
}

public class BinaryExpression extends Expression {
    protected final String field;
    protected final Object value;
    protected final Action action;

    public enum Action {
        EQUALS_TO,
        GREATER_THAN,
        LOWER_THAN
    }
}

public class MultiExpression extends Expression {
    protected final List<Expression> expressions;
    protected final Action action;

    public enum Action {
        AND,
        OR
    }
}   

Json Parser Hierarchy

public class JsonQueryRootParser implements QueryComponentParser<QueryRoot, JsonElement> {
    @Override
    public QueryRoot parse(JsonElement obj) throws ParseException {
        // Using JsonWhereParser & JsonSelectParser...
    }
}

public class JsonWhereParser implements QueryComponentParser<Where, JsonElement> {
    @Override
    public Where parse(JsonElement obj) throws ParseException {
        // Using JsonExpressionParser...
    }
}

public class JsonSelectParser implements QueryComponentParser<Select, JsonElement> {
    @Override
    public Select parse(JsonElement obj) throws ParseException {
        // Using JsonEntityProjectionParser
    }
}

public class JsonEntityProjectionParser implements QueryComponentParser<EntityProjection, JsonElement> {

    @Override
    public EntityProjection parse(JsonElement obj) throws ParseException {
        // Parse...
    }
}

public class JsonExpressionParser implements QueryComponentParser<Expression, JsonElement> {

    @Override
    public Expression parse(JsonElement entry) throws ParseException {
        // Parse...
    }
}
  • I read this question as: "Hi, I'm trying to solve a problem. The solution I'm considering is (content of post). What is the best design to achieve my goals?" The issue here is that you never defined the original problem or what your goals are. You've only described your current plan of attack. It's hard to offer you any meaningful help without the appropriate context. What is the actual problem? What are your goals? – MetaFight Feb 8 '18 at 19:04
  • I have a project in which I am creating API to expose data. I have the logical actions in which I can query and process the data before sending it. something like grahpql but with my own actions. now I need to be able to receive requests in json & xml and (for now) convert them to SQL to execute. in the future I want to be able to switch to another DB (Mongo for example) and still leave the logical actions the same (just change the execution) – matanper Feb 8 '18 at 19:13
1

Essentially you want to serialize/deserialize your model to/from a bunch of different formats. By model I'm referring to your 'logical hierarchy'.

Because every translator needs to be able to deal with every class of the model, you will not be able to do this with less than N*M translation functions, where N is the number of node types and M the number of supported formats.

Of course you might find an abstraction over your input/output formats, but then you still need to translate that abstraction to your actual DOM/string. This may be less work, but it will also restrict you to formats that fit your abstraction.

I'm not sure I would require the translator implementation to follow the class hierarchy of the model. In other words, what's the purpose of the QueryComponentParser interface?1 Every translator should be a module of its own, implementing just a simple interface:

public interface QuerySerializer<TOutput> {
    TOutput Serialize(QueryRoot model);
}
// and/or
public interface QueryDeserializer<TInput> {
    QueryRoot Deserialize(TInput input);
}

where TInput/TOutputmay be strings or DOM classes. Internally, a translator may well decompose into a similar hierarchy, but that's a coincidental detail.

Finally, your translators will depend on your model. That's fine. Their purpose is to translate that specific model, after all, not some generic model.

1 There may well be a valid purpose, e.g. if a request does not include an entire model, but only a part. Then you obviously need individual translators for each part.

  • Thanks. The separation for translators just seems more readable and maintainable to me. About the MxN translation functions I agree, but I taught maybe some design pattern like bridge could minimize the boilerplate – matanper Feb 11 '18 at 14:57
0

The biggest problem I see here is there is no model. Regardless of requests being in XML, JSON, YAML, SQL, or morse code and regardless of your persistence being in a traditional structured DB, a NoSQL DB, a file system, system memory, or beads on an abacus you should be using a model of what you're trying to represent when you respond to these requests.

Otherwise this turns into a fantasy where you're trying to translate every method of communication, every language, into every other language without ever once understanding what you're talking about.

  • Thanks for answering. I have number of types with some relations between them and I want to allow querying with hierarchy, projection, and filtering on these types. My problem right now is modeling the request (which define the query) and not the response. The logical hierarchy is my try to model this request, and now I'm struggling to adjust it to the different formats – matanper Feb 10 '18 at 13:24
  • Once you have a good understanding of your data model you can create one API/language that queries it and one that persists it. Neither of those languages should know anything about how you persist. Only how you model your data. You will write other code that does know how you persist. It will translate these data model languages into SQL, or xml, or etc. depending how what you decided to use to persist with today. Tomorrow you might use something else. – candied_orange Feb 10 '18 at 14:52
  • I know my data models, and I know how to query them. My model and the logical actions I am exposing are the root of the project. I am trying to make separation of layers like this: Query implementation (SQL) <-> query/actions model (the purpose of the project) <-> request format (json, xml) – matanper Feb 11 '18 at 9:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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