
Imagine the task: given the GlobalsDB or Caché dataset with their notorious globals; It is necessary to provide means of direct access GlobalsAPI, so that in nodes of a global type “^ n (“ Japan ”,“ Yamato ”) = $ lb (“ R.Keanu ”, 1000)" 0 to n; the second indexes are the names of the ships, also with restrictions. And the values for these indices would be the names of the captains and the number of their subordinates, of course, also with restrictions.
Immediately I will inform you that it is impossible to do this only with GlobalsAPI. Caché GlobalsProxy Framework offers a fast and versatile solution for such tasks. By combining Object-Globals Mapping (OGM) technology with GlobalsAPI methods, it is possible to process global data using objects (proxy classes) representing global nodes in the same way as ORM to relational databases - using objects representing tables.
')
Caché GlobalsProxy Framework main features:
- Means of creating descriptions of data structures and their limitations (metadata) of the global.
- Automated data validation.
- Generating proxy classes based on metadata.
- CRUD operations on data in the global using proxy classes.
All of this should generally increase the speed of development of software products that imply the use of GlobalsAPI for direct access to the globals of the GlobalsDB and Caché databases.
For entry into the topic, I recommend to get acquainted with: Problematics
When working with high-performance GlobalsAPI directly with globals, there is no need for metadata for them - this provides the extreme flexibility of data storage and reduces the cost of processor time for validation.
However, the fact that the availability of metadata is not necessary does not negate the fact that they are present in one form or another and, naturally, they must be taken into account. Also, at the moment there are no generally accepted rules for the organization of global metadata, respectively, developers have to invent their own ways of describing data structures, restrictions, semantics, decide where to store all this and how to work with it in the future.
However, even having figured it out, developers will also have to independently validate the data, because GlobalsAPI does not contain methods for processing them with any metadata at all, that is, only using it can lead to a violation of logical integrity.
Software development using GlobalsAPI is inconvenient and difficult, therefore, long and expensive. This can play a crucial role in a situation where it would be possible to sacrifice some of the productivity, but speed up development.
The Caché GlobalsProxy Framework provides the means to create a global description and, based on it, provides data processing using proxy classes while hiding the GlobalsAPI methods and logic of working with global metadata, in particular automating validation. All this, with a relatively insignificant loss of productivity, speeds up development many times over.
Providing global metadata
In order to be able to validate the data and present the nodes of the global proxy classes, the global description is first needed. For its storage is intended a specialized global - meta-global. Thus, data and metadata are stored side by side and accessible using the same API (step towards cross-platform).
For the description of the global version of the specification of the meta-global, which has the following form:
Meta-global specification:// Under semantics is meant the value / name of the entity in the subject area.
^ meta = $ lb (<name of the global, the meta information of which is described>, <semantics of the global>)
^ meta ("Indexes", <index number = 1..N>) = $ lb (<index semantics>, << type >>)
^ meta (“Values”, <for a node accessible by number of indices = [1; N]>) = $ lb (<semantics of a node>)
^ meta ("Values", <for the node available by the number of indices = [1; N], <value № = 1..V>)
= $ lb (<semantics of value>, << type >>)
^ meta (“Structs”, <structure identifier = 1..S>) = $ lb (<structure semantics>)
^ meta (“Structs”, <for structure with id = [1; S]>, <value № = 1..C>)
= $ lb (<semantics of value>, << type >>)
<< type >> =
<< * string >> = ("string", <maximum length>, <minimum length>, <default>)
<< * integer >> = ("integer", <minimum>, <maximum>, <default>)
<< * valid >> = ("double", <minimum>, <maximum>, <default>)
<< ** structure >> = ("struct", <structure identifier>)
<< list >> = ("list", << type >>, <minimum of elements>, <maximum of elements>)
<< array of bytes >> = ("byte", <maximum size>)
* Is a valid index type.
** - the structure can be used as an index type, provided that in it and in all structures nested in it, the types allowed for the indexes are contained.
For example:
Create a meta-global "^ nMeta" describing the global "^ n" with data on the naval forces of the countries:
Created meta-global^ nMeta | = | ("N", "Navies") |
Or, “nMeta” describes the global “n”, which stores information about Navies. |
^ nMeta ("Indexes") | = | $ lb (3) |
Or, the number of indices in this global "3" |
^ nMeta ("Indexes", 1) | = | $ lb ("Country", "string", 0,255, "Country") |
Or, the first global index stores information about “Country”, its type is “string”. Similarly: |
^ nMeta ("Indexes", 2) | = | $ lb (“ShipClass”, “struct”, “Classification”) |
^ nMeta ("Indexes", 3) | = | $ lb ("Name", "string", 0.255, "Name") |
^ nMeta ("Structs") | = | $ lb (2) |
The number of definitions of structures is 2-mind |
^ nMeta ("Structs", "Classification") | = | $ lb (2) |
Declaring a “Classification” Structure with 2 Names |
^ nMeta ("Structs", "Classification", 1) | = | $ lb ("ClassType", "string", 0.255, "ClassType") |
Or, the value “1” of the “Classification” structure stores information about “ClassType”, its type is “string”. Similarly: |
^ nMeta ("Structs", "Classification", 2) | = | $ lb ("Rank", "integer", 0,10,0) |
^ nMeta ("Structs", "ContactInfo") | = | $ lb (2) |
^ nMeta ("Structs", "ContactInfo", 1) | = | $ lb ("Name", "string", 0,255, "") |
^ nMeta ("Structs", "ContactInfo", 2) | = | $ lb (Phone, Integer, 1111111,9999999,1111111) |
^ nMeta ("Values", 1) | = | $ lb (2, "Manufacturer") |
Or in the nodes accessible by the number of indexes "1", information is stored on the "Manufacturer" with the number of characteristics "2" |
^ nMeta ("Values", 1,1) | = | $ lb ("Charge", "struct", "ContactInfo") |
Or at the nodes of level “1” the value number “1” stores information about “Charge”, type “struct”. Similarly: |
^ nMeta ("Values", 1,2) | = | $ lb ("Ports", "list", "struct", 2, 0.1000) |
^ nMeta ("Values", 2) | = | $ lb ("ShipCounter") |
^ nMeta ("Values", 2,1) | = | $ lb ("Count", "integer", 0.100500.0) |
^ nMeta ("Values", 3) | = | $ lb (“ShipInfo”) |
^ nMeta ("Values", 3.1) | = | $ lb ("Captain", "struct", 2) |
^ nMeta ("Values", 3.2) | = | $ lb ("StuffCount", "integer", 1,5000,1) |
^ nMeta ("Values", 3.3) | = | $ lb ("Efficienty", "double", 0,1,0) |
Fill global "^ n" according to the created description:
Filled global^ n ("Japan") | = | $ lb ($ lb (“Akihito”, 1534598), $ lb ($ lb (“Tokyo”, 5645122), $ lb (“Miaon”, 645122))) |
^ n ("Japan", "Battleship", 3) | = | $ lb (1) |
^ n ("Japan", "Battleship", 3, "Kawachi") | = | $ lb ($ lb ("IO Jaiodeen", 124234), 999, .7) |
^ n ("Japan", "Battleship", 10) | = | $ lb (1) |
^ n ("Japan", "Battleship", 10, "Yamato") | = | $ lb ($ lb ("LA Myolin", 142323), 2350, .6) |
^ n ("Japan", "Carry", 4, "Hosho") | = | $ lb ($ lb ("AM Harusimo", 134254), 548, .8) |
^ n ("Japan", "Carry", 7) | = | $ lb (1) |
^ n ("Japan", "Destroyer", 5, "Hatsuharu") | = | $ lb ($ lb ("WC Hawanio", 98034), 212, .65) |
^ n ("Japan", "Destroyer", 7) | = | $ lb (1) |
^ n ("USA") | = | $ lb ($ lb ("O.Barack", 1534598), $ lb ($ lb ("NH2", 4568976), $ lb ("MnP1", 987654))) |
^ n ("USA", "Carry", 4) | = | $ lb (1) |
^ n ("USA", "Carry", 4, "Langey") | = | $ lb ($ lb ("RC Adams", 9832723), 521, .65) |
^ n ("USA", "Carry", 4, "Saipan") | = | $ lb ($ lb ("PD Strong", 5234542), 1751, .75) |
^ n ("USA", "Carry", 7) | = | $ lb (1) |
^ n (“USA”, “Destroyer”, 5) | = | $ lb (1) |
^ n ("USA", "Destroyer", 5, "Nikolas") | = | $ lb ($ lb ("JC Denton", 5443123), 168, .75) |
^ n ("USA", "Destroyer", 6) | = | $ lb (1) |
^ n ("USA", "Destroyer", 6, "Farragut") | = | $ lb ($ lb ("OC Ohara", 1233422), 195, .8) |
^ n ("USA", "Kruiser", 4) | = | $ lb (1) |
^ n ("USA", "Kruiser", 4, "Omaha") | = | $ lb ($ lb ("PA Jenson", 1342344), 458, .65) |
It can be noted that using this variant of the meta-global specification, the structure, semantics and restrictions on the global indexes and values are uniquely set, as if it is assumed that the global data structure cannot be stored, thereby losing the storage flexibility. So it is in some sense, but it is only within the framework of one meta-global ... Nothing prevents to create several meta-globals for the same global.
Object Global Mapping
Now that we have provided global metadata, according to OGM technology, the nodes of the global tree must be represented by proxy classes. So, from the example above, nodes with semantics (ShipInfo), which store values about ships and are available by three indices, will be presented:
public class ShipInfoProxy {
Each proxy class has its own manager, which is designed to provide CRUD operations with data, while hiding the logic of working with global and metadata (in particular, data validation).
The ShipInfoProxy proxy class, in the context of this global, will correspond to the ShipInfoProxyManager <ShipInfoProxy, ShipInfoProxyKey> manager, the general view of which is:
public class ProxyManager<ProxyT, ProxyKeyT> where ProxyT : class where ProxyKeyT : class { public bool Validate; public ProxyManager(EntityMeta meta, TrueNodeReference globalRef, List<IStructManager> structsManagers = null); public ProxyT Get(ArrayList keys); public ProxyT Get(object key); public ProxyT Get(ProxyKeyT key); public List<ProxyT> GetAll(); public void Delete(ProxyKeyT key); public void Save(ProxyT entity); public List<ProxyT> GetByKeyMask(object key); public List<ProxyT> GetByKeyMask (ProxyKeyT key); }
Where, captain obvious, as if alluding to us:
void Save (ProxyT entity) - saves or replaces data in the global.
ProxyT Get (ProxyKeyT key) - reads data from the global node by the specified key;
void Delete (ProxyKeyT key) - removes a node from the global by the specified key.
public List <'ProxyT> GetAll () - reads values from all nodes;
But this rotation is a special method of data acquisition, which allows to get data from nodes, without specifying some indices of these nodes:
List <'ProxyT> GetByKeyMask (ProxyKeyT key) - reads data from all global nodes whose indexes correspond to the mask specified by the key.
All global node managers are accessible from a special context class that is responsible for their initialization:
public class NaviesContext : CacheEXTREMEcontext { public ProxyManager<ManufacturerProxy, ManufacturerProxyKey> ManufacturerManager; public ProxyManager<ShipCounterProxy, ShipCounterProxyKey> ShipCounterManager; public ProxyManager<ShipInfoProxy, ShipInfoProxyKey> ShipInfoManager; public WowsNiceContext(InterSystems.Globals.Connection conn) : base(conn, "nMeta") { this.ManufacturerManager = new ProxyManager<ManufacturerProxy,ManufacturerProxyKey> (base.entitiesMeta[typeof(ManufacturerProxy).Name], base.globalRef, base.structsManagers); ... base.structsManagers.Add(new StructManager<Classification> (base.globalMeta.GetLocalStructs()[0], base.structsManagers)); ... } }
Caché GlobalsProxy Framework
The Caché GlobalsProxy framework consists of the CacheExtremeProxy.dll dynamic link library and a metadata editor utility. To fully use it, namely to edit the data of the global, you need to do, conditionally, in 3 simple steps:
1. Creating Metadata
You can go the hard way, create metadata from the code, using the specially created GlobalMeta class, and then write them to the meta-global:
Creating metadata in code StructValMeta structV = new StructValMeta("ContactInfo", " ContactInfo " , new List<ValueMeta>(){ new StringValMeta(new ArrayList(){"Name","String",0,255,""}) ,new IntValMeta(new ArrayList(){"Phone","Integer",0,9999999,1}) }); GlobalMeta gm = new GlobalMeta("n", "Navies"); gm.AddStruct(structVal); gm.AddKeyMeta(new StringValMeta(new ArrayList{"Country","String",0,255,""}),"Manufacturer"); gm.SetValuesMeta(1, new List<ValueMeta>(){ new StructValMeta ("Charge", structV) ,new ListValMeta("Ports", structV) }); gm.AddKeyMeta(new StructValMeta ("ClassInfo", "Classification" , new List<ValueMeta>(){...}), " ClassType"); ...
And you can go a simple way, create metadata using the attached auxiliary application of the metadata editor:

Yes ... there is still "old school" - the way to create a meta-global using COS.
2. Generation of context and proxy classes
And again, you can go a relatively complicated way - code. Fill in a special class GlobalMeta (can be read from a previously created meta-global) and use a special class generator:
Generating context from code And you can, all the same, use the auxiliary editor application:

3. Connection and initialization
We connect the CecheExtremeProxy.dll library to the project, add the generated file with the context and proxy classes. Next, create connections with the database and use it to initialize the class-context.
four. ???????
5. PROFIT!
Now, metadata is available in the process of writing software code (of course, if the development environment supports InteliSense analogue), which doesn’t hurt that “what and where?” Is stored. Managers hide in themselves all the logic of working with the global and metadata, and, in particular, automatically validate the data. From all this, you can almost forget that direct access is used.
Examples of using
First we create connections with the base. conn = ConnectionContext.GetConnection(); conn.Connect(_namespace, _user, _password);
Next, you need to initialize the context:
Suppose we want to keep information about the new naval force. To do this, fill in the fields of the corresponding instance of the proxy class and pass it to the save method of the corresponding manager:
Then, if the data is correct, the following entry appears in the global:
^ n ("Great Britan") = $ lb ($ lb ("D. Cameron", 1112233), $ lb ($ lb ("Portsmun", 2223344), $ lb ("Dartford", 3334455)))
In case of incorrect data:

For comparison, to save a similar entry standard GlobalsAPI.And this is without checking the correctness of the entered data:
NodeReference r = conn.CreateNodeReference("wowsNice");
To access data in the nodes, an instance of the corresponding key-class is used:
Suppose we want to get all the ships on one condition - they must be of the 4th rank. To do this, fill in the remaining fields of the key class instance with empty values:
Testing
Test bench: acer aspire 5553g-n936g50mn.
Scenario: deleting a global, creating a global, writing in an object way (without validation), clearing the global, writing in the standard way.
Notes: single-threaded application, tester loaded 25% of the processor, writing to the hard disk.
Given the few under-optimizations for saving 2,000,000 records like this:
^ wowsTest (kruis, 0, [3, 0], 1) = $ lb ($ lb ($ lb (1, 111111), $ lb (2, 111111)), $ lb (3, 2, 1), $ lb (1, 2, 3), 1000, $ c (6,6,6))
[...] - in object view, this is structure
Results:
Object 00: 01m: 00s.1959, Native: 00: 00m: 29s.7478 obj / native = 2.023
Caché GlobalsProxy Framework for recording without validation took 2 times longer than GlobalsAPI. With validation - in 3.
Conclusion
At this stage, the Caché GlobalsProxy Framework is a working prototype, the use of which speeds up the software development process, which implies the use of direct access to the globals of the GlobalsDB and Caché databases.
Caché GlobalsProxy Framework completely encapsulates the logic of working with the global GlobalsAPI tools, as well as the logic of working with metadata, in particular, automating validation.
Eventually:
- Ensures logical data integrity. Now it is impossible to record anything and anywhere, since at first validation will be automatically done to the data, and only with its success will they be saved.
- The software development process using GlobalAPI is accelerated many times.
Buns:
- Creating your own types (structures) including indexes.
- Partial key data retrieval.
Of the minuses should be noted slowdown, but in some projects it is quite possible to sacrifice part of it in favor of development speed.
Development
At the moment, within the context of a global context, its data scheme is uniquely defined. This is accomplished by creating multiple contexts with different data schemes for the same global, which is a bit inconvenient. In the future, the various global data schemes will be combined in one context.
It is also planned to organize a combination of indices. In addition to storing data, for example, for all 3 indices, it is possible to additionally use a combination of 2 out of 3 of these indices and store different structures for them, again within the same context.
Project in open access
on github (GlobalsDB distribution kit is attached)
Guilty:
Gaydarzhi V.I. - for participating in the search for the project's theme and, most importantly, for believing in its favor.
Vityuk V.R. - for complicity in the specification and the creation of a metadata editor.
Kruchok S.I. - for the review.
Ancestors - for food and a roof over your head.
Nikolashin N.R. - for the review and most importantly - for the lack of faith in the project.