JCore API docs |
JCore overviewOpen source and object oriented frameworks for Object Pascal. Truly object oriented Pascal programming assisted by frameworks is a bit challenging. Integration between systems via REST protocol, serialization, deserialization and persistence of objects in a transparent way, dependency injection, integrations without coupling, flexible configuration logs, and the list goes on. Put it all together in an elegant and simple interface, while still being robust and reliable. There are few tools that meet these criteria. JCore is a tool that try hard to deliver most of these requirements to Object Pascal developers as a free and open source library. Although JCore has been designed with the concept of Convention over Configuration in mind, it is a premise of the framework to not create services or settings in the initialization area of their units. Therefore you need to manually declare what you want to use. On one hand it makes the initial settings a bit more verbose, on the other hand brings more flexibility and less unexpected behavior. Another premise used by JCore is to not use global variables. Despite the minimal configuration to build a "Hello World" be a little bigger, avoiding global variables allows you to design the architecture of your application with more freedom. It is for this reason that you need, for example, create and save an OPF configuration instance by yourself. JCore is divided into modules. The Core module has frameworks and reusable utilities. Each of the other modules depends only on the Core, for example, there is no dependency between OPF and Web Services. Thus if you only need OPF, add only Core and OPF to your project. There are Lazarus packages that can help you to include JCore as dependency, but using Lazarus package is not mandatory. If you don't use Lazarus just point the compiler to the directories of each module you want to add. There is also a Lazarus package with an expert that can help you build your JCore application from scratch. About Delphi compatibility: JCore is FPC first, and although Delphi compatibility is welcome, I cannot maintain it by myself. Let me know if you can fix the code and can be a maintainer of the Delphi compatibility — in this case write to joao.morais at pressobjects org. Welcome aboard! I hope you enjoy using JCore as much as I enjoyed design and build it. =) Contents:CoreJCore is divided into modules. This is good because although the product as a whole can become big and meet various kinds of needs, your project must declare and use only what it really need. This is also good for JCore's evolution because the evolution of a module does not impact another module which it does not depend. The Core module has frameworks and utilities reused by other modules. For example, logging and dependency injection features are useful to any module. Being small frameworks, they are declared in the Core module in order to be shared. Check out below its main features. Dependency Injection*** On the road Expression Parser*** On the road Logging*** On the road Types*** On the road OPFOPF — which stands for Object Persistence Framework — is a library that maps in memory objects to and from a permanent storage, such as a database or flat files. JCoreOPF is the OPF implementation of JCore. The following sections describe a little about this framework. ConfigurationThe building of a JCoreOPF environment begins with the creation of a global variable called Config. The Config responsibility is to maintain overall OPF data such as class and driver parameters, mapping classes, as well as model information such as OID class, generator, attribute types and metadata. The following code snippets show the most common parameterization of the Config instance.
The configurations above are the minimum necessary for building a functional JCoreOPF environment. The following topics discuss these configuration options in a little more detail. Time to take a cup of coffee. SessionTo add, find, change or delete objects you need to create Sessions. While the Config instance is thread safe, the persistent sessions are not. Important: in multithreaded applications, do create a new Session for each new thread. StoreUse the Store method to store a new entity or change an existing one in the database. uses // ... JCoreOPFSession; var // ... VSession: IJCoreOPFSession; VPerson: TPerson; begin // ... VSession := VConfig.CreateSession; VPerson := TPerson.Create; try VPerson.Name := 'jack'; VSession.Store(VPerson); // oops, wrong name VPerson.Name := 'james'; VSession.Store(VPerson); finally FreeAndNil(VPerson); end;
RetrieveTo retrieve an object whose var // ... VPerson: TPerson; begin // ... VPerson := VSession.Retrieve(TPerson, ['1']) as TPerson; try // ... finally FreeAndNil(VPerson); end;
The interface of the Retrieve method is unique to any OID type and expects the Id in string format. JCoreOPF converts to the correct format before searching on the database. DisposeTo delete an object from the database use one of two Dispose method approaches.
StatementUse statements to run queries or change data without using the JCoreOPF mapping.
CriteriaUnlike statements, criteria uses JCoreOPF mapping for querying the database. This way it can retrieve an object or a list of objects instead of a cursor.
Transaction control*** Unstable usage interface MetadataMetadatas are used to store model settings. Only part of the metadata can be configured directly at the entity's declaration, whose reading is made by JCoreOPF via RTTI. Other settings can only be made searching and changing the metadata at runtime. We look forward the FPC's property attributes implementation. =) Declaring a string of 50 chars in length and a composition: uses JCoreEntity; type // ... TPerson = class(TJCoreEntity) // ... private fields published property Name: string index 50; property Address: TAddress stored True; end;
Compositions cannot be shared with more than one object and are destroyed when the object that references it is destroyed (Dispose). If the Declaring a string of 100 chars in length and an aggregation: uses JCoreEntity; type TPerson = class(TJCoreEntity) // ... private fields published property Name: string index 100; property City: TCity stored False; end;
Aggregations can be shared among multiple objects and are not destroyed when the object that references it is destroyed. Important: while data within compositions are sent to the database whenever the main object is updated (Store), this only happens with aggregations when it does not yet exist in the database. To update the data from an aggregation — Declaring a list composition: uses // ... fgl, JCoreEntity; type TInvoiceItem = class(TJCoreEntity) // ... private fields and published properties end; TInvoiceItemList = specialize TFPGObjectList<TInvoiceItem>; TInvoice = class(TJCoreEntity) // ... private fields published property Items: TInvoiceItemList read FItems write FItems; end;
Important: note that the entity's memory management is a responsibility of the programmer. The code snippet above declares one of the types natively supported by JCoreOPF, however the control of the entity's life cycle and all its attributes should be treated. To complete the example above is necessary to overload the Because of a generics RTTI limitation on FPC, whenever declaring a generic list you must register it with the Model. The configuration below allows the JCoreOPF to know that begin // ... VConfig.Model.AddGenerics( TInvoiceItemList, TInvoiceItem);
The list compositions have the same behavior of simple compositions, ie, all items are destroyed when the main object is destroyed, and all the items that has changed will be updated in the database when the main object is updated (Store). Declaring a list aggregation: uses // ... fgl; JCoreEntity; type TLanguage = class(TJCoreEntity) // ... private fields and published properties end; TLanguageList = specialize TFPGObjectList<TLanguage>; TPerson = class(TJCoreEntity) // ... private fields published property SpokingLanguages: TLanguageList read FSpokingLanguages write FSpokingLanguages; stored False; end;
Worth the same hints of list compositions: destroy the list whenever you destroy the entity and use as a good practice to declare getter and setter to control the list lifecycle. List aggregations has the same behavior of simple aggregations:
JCoreOPF automatically recognizes the published properties and their types without the need for manual intervention. To adjust any setting that is not possible using RTTI, it is necessary to search and modify the metadata of a class or an attribute manually. Search the class metadata of begin // ... VConfig.Model.AcquireMetadata(TPerson).OIDGenerator := TJCoreOPFOIDGeneratorSequence.Create('seq_person');
Search the attribute metadata of begin // ... VConfig.Model.AcquireAttrMetadata(TPerson, 'Name') .PersistentFieldName := 'PERSON_NAME';
OIDEvery entity persisted in the database needs a means to be referenced when loaded into memory. In JCoreOPF this is done with OID management class and OID Generator instance. The former sets the class whose instance will control the OID data itself, the latter will be used whenever a new OID need to be created. Each of the model classes have an implicit OID that can be customized. If no customization is applied, JCoreOPF will create a GUID, store it in a field named Id, and expects that the field supports a string of 32 chars in length. To globally change the OID generator to the database's sequence uses // ... JCoreOPFOID; begin // ... VConfig.Model.OIDClass := TJCoreOPFOIDInt64; VConfig.Model.OIDGenerator := TJCoreOPFOIDGeneratorSequence.Create('seq_app');
If you prefer to use an auto-increment field or if the database does not support sequences: uses // ... JCoreOPFOID; begin // ... VConfig.Model.OIDClass := TJCoreOPFOIDInt64; VConfig.Model.OIDGenerator := TJCoreOPFOIDGeneratorAutoinc.Create;
It is possible to use a different generator for each model class: begin // ... VConfig.Model.OIDClass := TJCoreOPFOIDInt64; VConfig.Model.AcquireMetadata(TPerson).OIDGenerator := TJCoreOPFOIDGeneratorSequence.Create('seq_person'); VConfig.Model.AcquireMetadata(TInvoice).OIDGenerator := TJCoreOPFOIDGeneratorSequence.Create('seq_invoice'); VConfig.Model.AcquireMetadata(TCity).OIDGenerator := TJCoreOPFOIDGeneratorSequence.Create('seq_city');
Important: on inheritances, the same OID is shared between all subclasses. If a model entity inherits from another persistent entity, only the most abstract class may set a custom OID and generator. The contents of the OID is always available in IJCorePID.OID, see PID for information on how to declare the PID. *** Unstable usage interfaces, so missing doc:
The OID architecture of JCoreOPF was built in such a way to allow the use of compound OID, however this feature is not implemented yet. PIDPID is a JCoreOPF internal instance with persistence and metadata information of the entity, widely used in the internal implementation of the framewowk. There is a different A To declare an entity, use any of the following four approaches.
Which approach to use? Inherit the entity from TJCoreEntity if the entity does not have to inherit from another class, otherwise declare the The lack of a reference to the *** There is an undocumented feature regarding a native AttributesThe Mediation between the persistence engine and JCoreOPF's mapping is done through ADM — Attribute Data Mediator — instances. The creation and destruction of ADMs is handled by the framework. JCoreOPF has three distinct types of ADM implementations. All the types has the same performance, adopt one that suit you best.
Lazy loadingLazy loading is a feature that allows the entity to be partially loaded. Some attributes like blobs or lists might be configured to be loaded only if the information is actually required. Setting up a list of product items to be loaded on demand: type TInvoice = class(TJCoreEntity) private FItems: TInvoiceItemList; function GetItems: TInvoiceItemList; procedure SetItems(const AValue: TInvoiceItemList); published property Items: TInvoiceItemList read GetItems write SetItems; end; implementation // ... function TInvoice.GetItems: TInvoiceItemList; begin if not _proxy.Lazyload(@FItems) then FItems := TInvoiceItemList.Create(True); Result := FItems; end;
The lazy load syntax in depth:
How does the feature work? Whenever the getter of a property is set with a call to the LazyLoad method, JCoreOPF recognizes that call and flags the attribute to not be loaded from the database. The implementation of the getter delegates to JCoreOPF the responsibility to load the data. On success or if the data have already been loaded, the method returns The lazy loading of JCoreOPF works with any attribute type, although it makes more sense with lists and attributes that have a lot of content and little use. Generated by PasDoc 0.14.0. |