Elenesski Object Database
EODB and NEODB
The Memento Pattern Database for Unity
|
NEODB Is still under development (Jan 2016), at the moment we are developing a sample game to show how to use NEODB to create a game without a dedicated server.
These are some of the significant features of NEODB:
NEODB offers 133 different ways to find then process your data.
NEODB implements a "data push pull" architecture. This means that each APP (game or application) PUSHes the data they want to the SERVER (the PHP Server/database), and other APPs then PULLs the data down into their application. This architecture has it's strengths and weaknesses:
STRENGTHS
WEAKNESSES
NEODB does not provide security between the app and the server, and instead relies on it's users to provide it's own transport security; usually SSL on a server to support HTTPS. See the page "30 Network Architecture" in the main menu for additional information.
NEODB does provide application level security with a method to lock rows. Once a lock is issued, the only way to update, delete or unlock the row is to provide the correct lock ID. NEODB allows an application developer to come up with their own scheme for identifying LOCK IDs.
NEODB does not encrypt the data it sends from the app to the server. It's is highly recommended that you use a server that supports SSL so that you can make calls to your server using HTTPS. Without SSL/HTTPS, any hacker betweeen you and the server can see the information you are sending. See the section "30 Network - Architecture", in the main menu, for additional information.
NEODB does NOT provide any protection for Impersonation. In fact, every app is distributed to 3rd parties is subject to impersonation. The best defence against this kind of attack is to not store private, important, confidential, sensitive or secret information in your database or anything else that would make your environment a target for hackers and remember to back up your environment frequently.
NEODB provides the ability to implement exclusive locks. A "lock id" can be added to any record in the system, and remains in place until the record is "unlocked". If a Lock is specified on a record, then only updates, deletes, or lock clears on an object work ONLY if the LOCK ID you specify matches the lock id on the object.
In most cases, the way to implement locks is to tie it to a username/password object, and use the OID of the username as the LOCK ID. This way when a username is created only that username can be updated by the user that created it, and everybody is locked out from changes to it.
Data for NEODB is stored on the remote server in a mySQL database and the server itself does not maintain any session states. The main reason for this is to manage the amount of memory your remote server needs to process requests and to allow you to reimplement your NEODB on a server farm where multiple database threads don't have to be kept in sync. This allows you to scale NEODB on such platforms as AWS or Azure without have to worry about which thread your requests get sent to on the farm.
The Database consists of these 2 key tables, plus one table per index that you define (see below).
NEODB_OBJECT_DATA contains all of your objects. It consists of two columns:
NEODB_NEW_OID contains a single row which is the last OID created. It's incremented by the count passed to the ServerGetNewOID() method; default is 1. You must assign an OID to your object before calling your first SAVE otherwise you'll get an exception. You can still use negative numbers to indicate static data but for dynamic data the OID must be assigned before you save the data for the first time. Note: PHP issues a FOR UPDATE clause on the select allowing high performance database servers to have multiple concurrent threads requesting OIDs. FOR UPDATE prevents the same OID from being reissued when two threads request OIDs at the same time.
NEODB uses exceptions to communicate to your APP about the failure of your NEODB request:
// Thrown when NEODB does support a feature of EODB in NEODB. Usually there is a Server or Local equivalent of it NEODBNotSupportedException // General exception thrown when the request fails. NEODBGeneralException // Thrown when the request server request was too old. NEODBSessionExpired // Thrown when the request sequence is different than expected. This could be an internal error or a hijacking attempt. NEODBSequenceError // Thrown when lock setting or clearing doesn't work NEODBLockError
// Standard connection string details for the mySQL database $NEODB_HOST = "127.0.0.1:3306"; // HOST IP and PORT. Can be a DNS name too. $NEODB_DBNAME = "database_name"; // NAME of the database within the mySQL Instance $NEODB_USER = "database_user"; // USER with read/write access to the database $NEODB_PASSWORD = "!!Password!!"; // PASSWORD of the user // Indexes; see documentation ... consists of a name and a column definition $NEODB_INDEXES = array( array ("NAME","varchar(25)"), array ("LEADER","int") );
Normally you can only make one server call at a time. This is why we created NEODBChain. There are situations where more than one database call has to be made at the same time. To solve this problem, you simply use multiple NEODB object instances and connect them to the same PHP server.
In order to be successful with NEODB, you need to wrap your head around the asynchronous nature of the calls. Failing to do this will result in code that does not work the way you expect it to. The biggest difference is that when you call the server asynchronously, control returns to you immediately and the actual server response will be seconds later through success or failure method calls.
In the local database version EODB, when you make a call to the database to perform some task, that call goes away, does some work, and returns with an answer. Your code will wait until the database has finished.
However, when using the network version of NEODB, when you make a call to the database to perform some task, that call returns right away. While NEODB is contacting the database, your application will need to perform some sort of task to let the user know a request is being processed. This could be a spinning busy icon or perhaps you can do work on the canvas while it waits for an update to take place.
In the background, NEODB calls the server, and a few moments to 1 minute later, it gets a reply. The reply is either successful or a failure. We hope that every call will be a success, however, networks can be finicky and fail for reasons having nothing to do with us. As a result, your APP will need to deal with several types of failure for every call it makes. Because any one of these possible reasons could occur, and they can occur at any time:
As a result, you must be prepared to handle all of these possible errors in your code.
Fortunately, NEODB makes it easy to handle asynchronous calls with callbacks. Every NEODB method that involves the server has at least two method references, one for when a successful call completes and the other for failure. Either of the two is called when the SERVER finally responds with information.
_TheNetwork.ServerGetToken(GotToken,NetworkFailed); private void GotToken() { // Called when we successfully got a token from the database } private void NetworkFailed(Exception aException) { // Called when our call failed. aException contains information about the failure. // We have decided to not intercept the exception, and just throw it. throw aException; }
When working with classes that implement IEODBClass, you will need to construct a EODBDescriptor with additional parameters to identify the success method and failure method. Failure to do this will cause an exception to be thrown, expecting the Sucess and Failure methods to be identified. The only difference between the two are the constructors, which defines a
new NEODBDescriptor(aOID, this, false, SuccessfulLoad, FailedLoad); private void SuccessfulLoad() {...} private void FailedLoad(Exception aException) {...}
Dynamic Indexes operate the same as regular indexes with three exceptions:
Dynamic Indexes are used for situations where the data is temporary, such as events. For example, in the Online Poker Example, the host creates events to communicate with players for a given hand. When the hand concludes, those events are deleted and new set of events are created for the next hand. This allows players to enter the game, or crash and reload, without the host having to manage the sync'ing of new players.
NEODB offers the following FIND methods for finding data on an index:
While these find capabilities are extensive and do cover several a lot of searching needs, if you find you need to search, filter, then search again, resulting in more than one trip to the server, you will probably need to create an Intelligent Index.
Intelligent Indexes serve a specific purpose; to enable a single trip to the server to find information for your game. Basically you want to create values in an index that would allow you to get exactly the objects you need for your game by using a "ServerFindEquals" call.
The way it works is:
Using this strategy, you can now use the index to get only the objects that meet your search criteria without having to "select then filter".
The problem with a post selection filter is the time it takes to find and transport all the objects your initial selection takes. Assuming each object is 1K, If you have to pull out 150 objects to display 3, that means 147 objects have to be selected and transported to the app, only to be thrown away. Clearly if you could select only the 3 you needed,
This is an important architectural strategy when implementing NEODB. Many junior developers make a simple mistake when developing that causes a lot of work/pain later, that could have been avoided. They choose a technology like NEODB and then begin to integrate the technology directly into their code. This approach is a bad idea, and this section is to discuss why this is a bad idea and offer about a better approach.
This approach should be adopted for all libraries you buy and plan on using!
While NEODB is a great environment for storing your objects in a remote database, it has some limitations. It's biggest limitation is that pushing and pulling data from a database is time consuming and potentially expensive if you have a lot of people doing it, and it may become apparent that a different networking solution is required, such as Unity's Networking package.
By directly interfacing your code into NEODB, means that if you make the decision to replace NEODB with something else, every place you wrote code that used NEODB will now need to be rewritten and replaced. This can cause a huge maintenance headache as there may be dozens or hundreds of places where this integration took place. The maintenance is created because you "coupled" your code to the library. For more information on coupling, see Wikipedia Coupling (link).
A better strategy is to decouple the NEODB from your code using a Façade.
Façade is a GoF pattern that creates an interface between two systems, or in the case, between NEODB and your code. For more detailed information see Wikipedia Facade (link).
The façade, implemented as a new class, allows you to create methods that describe the behaviour of what you want NEODB to do, then create methods that implement this behaviour. While it may see a bit of overkill to use a new class to interface to NEODB, but it will save you hours of work later if you need to replace you did something with NEODB or ultimately have to replace NEODB (or EODB) later with something else.
It can even save you time as you develop, when you want to use the same behaviour in more than one place in your code.
It's a better strategy too when attempting to revise objects, fixing bugs, etc. In a job many years ago, the developers did none of this and simple changes which should have taken 10-20 minutes took days because there were hundreds of places where the same code was implemented, that had to be changed and retested.
Essentially what it allows you to do is see, in a single place, all the things you wanted your networked database to do, and replace the code only in one place. It's still maintenance, but you've contained the maintenance to a single class which is means you know your code has been upgraded to use the new environment and it's easier to test the changes.