⬆️ ⬇️

Mozart CMF: XML-based API

So, in my first article I made several assumptions about the architecture of the subject area in CMF / CMS systems . Then I made an assumption about the object model, I associated with her service, which was able to handle incoming requests and produce the desired result based on the internal structure of the model.



Today, now, I would like to touch upon the issue of directly using such a system in the existing Mozart framework, recently introduced by ADV under the GPLv3 license.



The Mozart API is based on newt-containers. Newt is an XML container (in an XML fragment) that is specially processed by an XML parser. Newt can be either a single tag (closed <newt /> tag) or a container (contains other containers (newt's, instructions)). The analogue of newt is a function whose task is to perform some actions (and return the result, if required).

')



The XML parser generates events as XML data is processed, and program handlers are hung on these events. 2 events are important for us: onStartTag and onEndTag . All newts are processed on onEndTag . This gives us what all nested newt's (nested instructions) first recursively work through, and then their parents. Those. each nested newt generates some content for its parent newt, and the parent newt can use this XML fragment for its intended purpose (simply as content or as its instructions (since its body is already completely defined and completed by the time it is processed)).



In addition to other nested newt containers, a newt container may contain instruction containers (a specially designed XML description that tells newt what to do). Instructions are framed in a special tag (for example, request or request-data ) to distinguish them from a simple XML fragment, and can be arbitrary according to the functionality inherent in the program: saving to the database, reading from the database, setting parameters, etc. For each container, a context is also defined that can be controlled. The current instruction in the current context (which it has received) is executed. Sets the context for the downstream instructions.



Now apply this knowledge on a specific example. As such, we take samples from the database, as the most basic and frequent component of any project. The container <newt: base> is responsible for this function in the system.



The peculiarity of working with data in Mozart can be considered that it is necessary to operate with them in the system as with objects. Imagine all the tables in the Database, their relationships as a set of terminological field objects interconnected. These objects can be given "human" names and build a graph, as in the figure below.







The life situation of creating a system for a mobile operator can be depicted as a set of real-life objects interconnected. The graph describes the situation with cellular communication: operator - region - tariff - sim card (number) - contract - telephone - brand - person - where it is registered.



Based on this structure, we can build arbitrary queries to retrieve data. Take for example a few situations in which different conditions are used:





What brand are used by people whose age is from 20 to 30 years:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < set object ="user" attr ="age" min ="20" max ="30"> <br/> < get object ="brand" /> <br/> </ set > <br/> </ request > <br/> </ newt:base > <br/>




To limit the age of a person, we used the operators min and max . By exposing them to the user object, we inserted a sample of the brand object inside. Nesting links a condition and a sample; Mozart builds the connection between the two objects through the Phone object connecting them (see how they are linked in the graph above).



List of people using the services of a specific operator:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < set object ="operator" attr ="name" value ="Example"> <br/> < get object ="user" /> <br/> </ set > <br/> </ request > <br/> </ newt:base > <br/>




Here we used the equality operator to set the name parameter of the operator object to a specific value. The user object will be selected taking into account the conditions of the operator. If you look at the graph, you can see that Operator and Man are connected by two different routes. Mozart selects the shortest one when building links. Read more about this in the article " Domain Architecture in CMF / CMS Systems ".



A list of people whose last name contains a given sequence of characters:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < set object ="user" attr ="first_name" context-attr =""> <br/> < get object ="user" /> <br/> </ set > <br/> </ request > <br/> </ newt:base > <br/>




The context-attr used by us in this example indicates a substring that should be contained in the first_name parameter of the Person object. This is a kind of contextual search.



Above, we mentioned how Mozart is trying to build a connection between non-adjacent objects. Delve into this moment a little more. For example, in the second example, when we chose Person by Operator, we assumed that Mozart could build a connection along two routes (through Registration or through Region, as can be seen in the graph). If, for example, we need to change the Mozart algorithm and point out that the short route does not suit us in a particular situation, we can use the path-exclude operator for the get instruction, specifying a list of objects that we don’t want to include in the linking ones (by analogy there is a path-include statement). Below we exclude the Phone object:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < set object ="operator" attr ="name" value ="Example"> <br/> < get object ="user" path-exclude ="phone"/> <br/> </ set > <br/> </ request > <br/> </ newt:base > <br/>




Perhaps someone may have a question about the efficiency of such a system that tries to build connections along several routes at once, wasting CPU time and memory, and thus slowing down the application. In this sense, everything is solved very simply: when designing a diagram of objects, we should avoid looping. In our case, it is likely that the Registration object should be marked in the base configuration as non-transit, i.e. such an object that is always finite when building relationships.



Let's talk now about the nesting of some get and set instructions in others. Consider 2 examples:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < get object ="user" > <br/> < get object ="phone"> <br/> < get object ="brand"/> <br/> </ get > <br/> </ get > <br/> </ request > <br/> </ newt:base > <br/>




and



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < get object ="brand" > <br/> < get object ="phone"> <br/> < get object ="user"/> <br/> </ get > <br/> </ get > <br/> </ request > <br/> </ newt:base > <br/>




Both examples operate on 3 objects: brand, phone and user, which are connected.



The first example will bring us all users, for each of them a list of phone models that this person uses will be displayed. For each phone, the phone brand will be displayed. Get the structure:



Copy Source | Copy HTML<br/> < user name ="1"> <br/> < phone name ="example123"> <br/> < brand name ="Super Brand" /> <br/> </ phone > <br/> < phone name ="345example"> <br/> < brand name ="Super Brand" /> <br/> </ phone > <br/> < phone name ="678example678"> <br/> < brand name ="Brand N 1" /> <br/> </ phone > <br/> </ user > <br/> < user name ="2"> <br/> < phone name ="example123"> <br/> < brand name ="Super Brand" /> <br/> </ phone > <br/> </ user > <br/> < user name ="3"> <br/> < phone name ="345example"> <br/> < brand name ="Super Brand" /> <br/> </ phone > <br/> </ user > <br/>




The second example will show us a list of all brands, each will display a list of all the phones of this brand. And already inside each phone we will receive a list of people who use this phone model.



Copy Source | Copy HTML<br/> < brand name ="Super Brand"> <br/> < phone name ="example123"> <br/> < user name ="1"/> <br/> < user name ="2"/> <br/> </ phone > <br/> < phone name ="345example"> <br/> < user name ="1"/> <br/> < user name ="3"/> <br/> </ phone > <br/> </ brand > <br/> < brand name ="Brand N 1"> <br/> < phone name ="678example67"> <br/> < user name ="1"/> <br/> </ phone > <br/> </ brand > <br/>




If, for example, you want for some reason in the first example for a phone to display not its brand, but generally the entire list of brands, for this you can use the unset instruction.



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < get object ="user" > <br/> < get object ="phone"> <br/> < unset object ="brand"> <br/> < get object ="brand"/> <br/> </ unset > <br/> </ get > <br/> </ get > <br/> </ request > <br/> </ newt:base > <br/>




Those. we removed the conditions placed on it from the brand object, thus it is simply a sample of all data from the object.



If we want to display a list of all users, all models and brands of phones, then simply get rid of nesting and write the get instructions sequentially:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < get object ="brand" /> <br/> < get object ="phone" /> <br/> < get object ="user" /> <br/> </ request > <br/> </ newt:base > <br/>




Above, we talked about object fetching and get instructions. And what happens in a situation when it is necessary not to select several objects consecutively, but to select only one object, while setting restrictions on several others? Let's return to our second example (where Brand was chosen, his Phone and the Person using it). Redo it in this:



Copy Source | Copy HTML<br/> < newt:base > <br/> < request > <br/> < set object ="brand" attr ="name" value ="Brand N 1"> <br/> < set object ="phone" attr ="name" value ="678example67"> <br/> < get object ="user"/> <br/> </ set > <br/> </ set > <br/> </ request > <br/> </ newt:base > <br/>




We replaced the get Brand and Phone objects with a set , i.e. to set limits on them. The nesting structure remains the same. Thus, we have indicated to accumulate restrictions for the most recent get , which selects the Man object, taking into account the above set instructions in which it is nested.



As a result, we get a list of users who use these phones:



Copy Source | Copy HTML<br/> < user name ="1"/> <br/> < user name ="10"/> <br/> < user name ="11"/> <br/>




findings





As we have seen, the Mozart API has an obvious structure and simplicity, which allows you to operate with a sample of data and their classification to inexperienced developers. The result of the data is a structured XML format, the structure of which is completely dependent on your requests. You can add some redundancy to it, maybe not. You have the opportunity to receive such XML trees, which will be convenient not only for further processing in XSLT, but also for the usual visual perception, because it is the simplicity of project development that is fundamental in choosing a development system.



The terminological field of objects helps to understand the structure of the project, the relationship between its data. If you get rid of technical terms, SQL queries and other low-level subtleties, you get an environment that is very similar to the outside world, you operate in it as in the real world.



For the seed on the next article I can give other elements of the API. For example, <newt: form> is responsible for processing forms: output, receive and process data. <n ewt: transform> - converts one data structure into another, with its help you can create instructions for higher-value newts, as well as convert one xml into another. <newt: action> is designed to execute controllers defined in the web application. In fact, this is one of the ways to invoke scripts written by a developer in any scripting language. <newt: http-env> is a handy tool for working with environment variables, be it a cookie, a session, or a user dataset called query.



Mozart website: mozartframework.ru

Source: https://habr.com/ru/post/88877/



All Articles