Zing implements most of your server code and large portions of your web client. The following diagram captures the major components of a Zing application.
There is an example Zing application which we work through step by step. However, it may help to read this overview first to get a sense of the pieces before building anything.
Type definition
Development of a Zing application begins with the type definition. This is where you specify the kinds of data objects that will be stored on the server by you application and will be used in the client to provide the user experience. You specify your data types using JSON, a text editor and the Zing data generator. The data generator converts your specification into TypeScript declarations. You then add your own code to these declarations. All generated type code inherits from the DataObj class that defines the basic mechanisms for communicating and storing data objects.
The generated code has everything necessary to communicate with the rest of the Zing data system. The data generator also creates the code that will marshal your data objects into JSON and will parse JSON into your data objects. This provides the basis for the HTTP communication between client and server.
By generating this code identically on both the client and the server, many communication errors are eliminated.
Data sources
Data sources are TypeScript classes that provide your code with access to data storage and communication. These all inherit from the DataSource class. They all function in terms of the DataObj class from which all your generated types inherit.
Your program will function by calling standard methods defined on your data objects. These methods in turn will call the RightsManager for permission and will call DataSource methods to access those objects.
There are currently 4 subclasses of DataSource.
- Client data sources
- HTTPDataSource – communicates with a ZingExpress server using the HTTP protocol
- CacheDataSource – this provides a caching facility on top of another data source. This is almost always used in the form “new CacheDataSource(new HTTPDataSource())”. This allows objects to be cached locally in your client to increase performance. The CacheDataSource makes sure that any changes that you make will be propagated through its parameter data source automatically. The CacheDataSource adds a lot of speed to your client without you needed to deal with most of the client-server data consistency problems.
- Server data sources
- MongoDataSource – this translates DataSource calls into calls on a MongoDB database. This provides persistent storage for your data objects on the server. The programmer does not need to learn MongoDB, only the DataSource interface.
- other databases – it is straightforward to implement versions of DataSource that can use other databases or even the file system for the storage mechanism.
- MemDataSource – this is an in-memory data storage mechanism. It is useful for debugging and regression testing.
Rights Management
In any realistic application, not everyone is allowed to do everything. Some rights are restricted for the protection of all users. In Zing this is centralized in the RightsManager class. The default RightsManager class will approve everything and provides no protections. An application can create subclasses of RightsManager to provide the specific controls needed for that application. Most of the methods on DataSource and DataObj first pass through the RightsManager for permission to perform that action. All of this permission requesting and granting is embedded in Zing static or generated code. This ensures that rights management is done consistently throughout the application.
As shown in the architecture diagram above both the client and the server are initialized with a RightsManager. If these are both the same class, then the same rights will be enforced in both places. Enforcing rights only in the client leaves your application vulnerable to spoofing attacks on your server. If the rights are not enforced by the server the application is highly vulnerable. Enforcing rights only on the server will degrade the user experience. We would like to information uses immediately when they are about to do something inappropriate. If we enforce rights in two places we want those rights to be consistent with little effort on the part of the developer.
In some situations enforcement may be different between client and server for implementation or efficiency reasons. This is best done by creating a common RightsManager subclass for the entire application and then creating subclasses of that for those situations where enforcement on the client is different than on the server.
ZUI – Zing User Interface
ZUI is designed to rapidly build complex user interfaces for client server applications. It uses the one-page design model found in Angular. An app only loads one page and then switching between user pages and screens is all done locally in JavaScript/TypeScript. ZUI is intended to target multiple interactive platforms and as such it has an architecture much like React Native. At present it only generates web applications using JQuery.
There are two parts to ZUI: page management and UI components. When a client is initialized it is given a UserManager implementation, a PageManager and a set of registered pages. One of the pages is designated as a home page. Every page has a root ZUI component that defines the user interface for that page.
There are a wide variety of ZUI components that are all subclasses of ZUI. Each Page has a ZUI component that defines its UI.
When a new client is started it is given a PageManager that receives a DataSource, a home page to start from and a JQuery selector that specifies where in the HTML the PageManager should put its user interface.
Sample Client
A client is completely defined in TypeScript. The HTML in which it is embedded comes form the server and can be seen in the sample server code. The following is an example client file.
1) let httpSource = new HTTPDataSource(window.location.origin+"/"); 2) let source = new CacheDataSource(httpSource); 3) 4) DataObj.globalSource = source; 5) let rm = new ClientRightsManager(source); 6) source.setRightsManager(rm); 7) ZUI.pageManager = new PageManager(source, 8) new LoginPage(null),"#content");
Line 1 creates a new HTTPDataSource that will perform all DataSource operations across the web using HTTP. This is preimplemented in Zing and takes care of all of the transfer of DataObj and login operations across the network.
Line 2 creates the CacheDataSource that our client will actually use. This uses the httpSource created in line 1 as its actual source. It then provides all of the caching that will speed up our application. By using CacheDataSource we repeatedly make necessary DataSource calls without worrying about network costs. CacheDataSource retrieves items from RAM wherever possible.
Line 4 tells the DataObj class where to find its data source. In most cases we work with subclasses of DataObj rather than directly with DataSource. The idea is that we just manipulate our objects and Zing takes care of locating them and getting them saved.
Line 5 sets up our ClientRightsManager and gives it access to the DataSource.
Line 6 tells the DataSource which RightsManager to use.
Lines 7 and 8 set up the PageManager that will manage the user interface. The pages registered with the PageManager along with the ZUI components define our user interface.
PageManager
To create the rest of our client we create subclasses of Page for all of the various pages that we want for our user interface. On line 8 above we specified that LoginPage would be the home page for our application.
In addition to its constructor the PageManager has three key methods:
- PageManager.registerPageFactory(pageName:string, factory:(state:PageState)=>Page)
- PageManager.PUSHTO(pageName:string,newState:PageState)
- PageManager.GOTO(pageName:string,newState:PageState)
When creating a new Page class we afterwards need to register that page the the PageManager so that it knows how to create a new one when requested. This is done using registerPageFactory(). Factories can be as simple as returning a new Page of the correct subclass or they can take other information into account when creating the new page.
The PUSHTO and GOTO methods are for changing to a new page. The PUSHTO method will push the current page onto the stack where it can be accessed via the back button of the browser or other back button mechanism. The GOTO method simply changes the page without impacting the back stack.
Page
The Page is the high level structure tool for a ZUI user interface. Page is itself a subclass of ZUI. When a Page is constructed it is given a PageState object which is just a dictionary of variable names to strings. This is used by the page to know what data it is to use in performing its function. The PageManager will translate this state information into the query part of a page’s URL. This will allow links to be made directly to a Page and its information. Usually the PageState will contain keys for the objects that the page is to interact with.