Click here to Skip to main content
15,880,503 members
Articles / Programming Languages / C#

Remote Object Repository

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
28 Jan 2015CPOL9 min read 16.1K   173   16   1
Repository layer which creates proxy class for an object which is implemented in a different component.

Introduction

This article introduces a client-server architecture where the communication between the two sides is completely hidden. In this architecture, when calling a method you don't even have to know if it is executed locally or remotely. Suppose that we have a Calc class on the server side that implements ICalc interface and contains the logic how to add or subtract two numbers. When we are about to add two numbers on the client side, we would like to use that implementation. With our solution, it is possible to instantiate a proxy class on client side which implements the ICalc interface and when you invoke its Add method, the call will be automatically forwarded to the server side where it will be evaluated. So it is transparent to the client that the real ICalc implementation is on the server side and if we use DI container to get a new instance, it is absolutely hidden where it is implemented. For the caller, the proxy object seems like a common local instance so the property getters and setters, the event subscription and unsubscription and method invocations work as normal.

However, you always have to use the proxy classes carefully because the communication overhead can cause performance problems. To reduce this problem, we introduced journals to batch remote operations before sending them. Whenever a journal is opened, then no remote call would be started till the journal is closed. The remote calls are queued in the journal and when the journal is disposed, all the calls are sent at once for the remote side.

Background

We utilize two main components of our libraries to achieve our goals. The DynamicProxy helps us to generate fake implementations for interfaces and our remoting layer responsible for conducting the remote calls. This is covered in my first article.

We use our Remote Operation Layer to start remote calls between the remote object repositories in different components at different locations. This is explained in my second article.

Using the Code

The diagram below shows the high level architecture. The architecture is symmetric, each side can instantiate proxy classes what is implemented on the other side.

Image 1

First, we create a class inherit from the abstract DynamicProxy class. This RemoteObjectRepositoryDynamicProxy class describes what happens when the proxy interface implementation class gets a call (see details in Dynamic Interface Implementation article). The main idea is when a call is received, this class does a remote call to execute the local call on the remote side where the real implementation is. But we try to keep the traffic low so we store the state of the remote object in local. So the proxy object can return property values without network traffic. To do this, we need to get the initial state of the object after it is created on the remote side and if the state is changed on the remote side, the local proxy object should be notified and get the new value.

This notification event is defined in the IRemoteCallable interface which should be implemented by all proxy object. If the proxy object is IRemoteCallable, then the RemoteObjectRepositoryDynamicProxy class subscribes to this event and handles the event when a new value is coming.

C#
/// <summary>
/// Interface for objects which can be call from the remote side
/// </summary>
public interface IRemoteCallable
{
    /// <summary>
    /// Raised when remote property changed
    /// </summary>
    event EventHandler<RemotePropertyChangedEventArgs> RemotePropertyChanged;
} 

We lower the network traffic with journaling too. This means when a remote call is needed, then the proxy writes the operation to the journal if it is possible. The property sets and event subscriptions can be stored in journals but the method invocations is always sent to the remote side immediately.

The journal is handled by DynamicProxyJournalManager. This class implements the related interface IDynamicProxyJournalManager. It only contains one method.

C#
/// <summary>
/// Journal interface to pile calls up
/// </summary>
public interface IDynamicProxyJournalManager
{
    /// <summary>
    /// Start new journal. While the journal is active no remote call 
    /// will be started just collect the calls.
    /// When journal disposed all the collected calls will be sent.
    /// </summary>
    /// <returns>Journal as IDisposable 
    /// which can be disposed to finish the journal</returns>
    IDisposable StartJournal();
}  

The StartJournal method creates a new journal if there is no current journal. Journals are created per thread or task. If a journal already exists for a thread, then that instance would be returned. The return value type is IDisposable because the only important attribute of the journal from the callers point of view that it is disposable. So the start method should always be used in using blocks. The manager stores the reference numbers of a journal and if the last reference is disposed the journal, the manager executes only one remote call and passes all collected operations to the remote side. There, the operations are executed in a batch. The remote calls are initiated by the RemoteObjectRepository.

The RemoteObjectRepository manages the communication with remote object repository of the remote side. Because its task is symmetrical, it has some Local and Remote postfixed methods. For example, ExecuteBatchRequestRemote responsible for calling the other side’s ExecuteBatchRequestLocal methods.

The CreateObjectRemote and CreateObjectLocal pair are responsible for instantiating a new proxy object and a real object pair. The CreateObjectRemote is registered in the DI container as the creation methods for the remotely implemented interfaces. The method first creates an id for the new object. This id will identify this new object on both sides and this id will be sent through the network. This method uses a DynamicProxyFactory to create proxy objects. Register this object in the local remote object repository with the new id. Then, it creates a new RemoteOperation which contains the information for the remote side to create the real instance with the same id. Then the operation is stored in the current journal. During the execution of the journal, the CreateObjectLocal will be called on the remote side. This gets a new real instance from the DI container and registers it in the remote object repository with the given id. Then the repository subscribes for all the events of that object so it can notify the caller side when an event is fired. Finally, the initial state of the object is queried and returned. (The state of the object means the value of the properties).

C#
 private object CreateObjectRemote(Type interfaceType)
{
    object ret = null;
    if (CheckRemoteTypeAvailable(interfaceType))
    {
        RemoteObjectRepositoryId newId = RemoteObjectRepositoryId.NewId();
        RemoteObjectRepositoryDynamicProxy p = 
        (RemoteObjectRepositoryDynamicProxy)factory.CreateDynamicProxy
        (interfaceType, interfaceType, this, journalManager);
        ret = p;
        RegisterNewObject(interfaceType, ret, 
        newId, p.HandleEvent, p.InitializeHandler, p.DisposeInternal);
        using (journalManager.StartJournal())
        {
            journalManager.AddJournalItem
            (new RemoteOperation(RemoteOperationType.Initialize, ret, interfaceType, null));
        }
    }
    else
    {
        throw new InvalidOperationException(string.Format
        ("The {0} típus not registered as remote callable!", interfaceType.FullName)); //LOCSTR
    }
    return ret;
} 

public object CreateObjectLocal
(Type interfaceType, RemoteObjectRepositoryId newId, HashSet<string> propList)
{
    object ret;
    object obj = diContainer.GetLazyBoundInstance(interfaceType).Value;          
    RegisterNewObject(interfaceType, obj, newId);
    SubscribeAllEventLocal(obj, interfaceType, newId);
    ret = GetObjectCurrentValues(newId, interfaceType, propList);
    return ret;
}

After the creation, we have an object in our hands which implements the requested interface, this is a proxy object stored in the local remote object repository with a RemoteObjectRepositoryId, the remote side’s remote object repository contains an object which is a real implementation of the requested interface and has the same id. So you can use the proxy object like the real implementation of that interface.

C#
public abstract class RemoteObjectRepository : 
    IRemoteObjectRepository, IRemoteObjectRepositoryInternal
{
    private IDynamicInterfaceImplementor interfaceImplementor = null;
    private DynamicProxyFactory<RemoteObjectRepositoryDynamicProxy> factory = null;
    private IDIContainer diContainer = null;
    private DynamicProxyJournalManager journalManager = null;

    protected abstract int MyComponentID
    {
        get;
    }

    protected abstract TReturnType ExecuteOnRemoteSide
    <TReturnType>(RemoteOperationDescriptor rso);
    protected abstract void ExecuteOnRemoteSide(RemoteOperationDescriptor rso);
    protected abstract IEnumerable<Type> GetTypesImplementedByThisSide();
} 

The RemoteObjectRepository is an abstract class and has some abstract members. There are two ExecuteOnRemoteSide methods. These are responsible to manage the remote calls and should be implemented in each side. There is a MyComponentID property which identify the remote object repository.

If an interface can be used from a remote location, it should be marked with RemoteObjectRepositorySupportedTypeAttribute and this attribute needs a component id representing which side contains the real implementation. And this is why we need the last abstract method, the GetTypesImplementedByThisSide. This method returns the list of the interface types which have the real implementations in the current component. This method is called in SendRemoteTypesAvailable which process this list and send it to the other side. When the other repository gets the list of the types, it registers these interface types in the DI container and sets the creation method of the proxy class to the above mentioned CreateObjectRemote method.

Image 2

As you can see in the downloadable sample code, it is really easy to set up an environment for this library. In the sample, I use the ICalc interface which is attributed with RemoteObjectRepositorySupportedType attribute with a parameter that indicates that this interface is implemented on server side (ComponentId.Server). I have to implement the RemoteObjectRepository abstract members on both project (ClientROR, ServerROR classes). The important thing is that the client side immediately calls SendRemoteTypesAvailable and sends its classes and the server side calls this method when it gets the client side's types. The server side first sets up the communication objects and then creates the server-side RemoteObjectRepository and waits for the call from client side. The client side also sets up the communication classes and its RemoteObjectRepository. After the setup, you can get an ICalc implementation at client side and call its methods. These calls will be executed at server side where the logic is. This is just a simple usage. You can try to use properties and events also or you can place classes on both sides or create a server remote object repository that supports multiple clients (as we did in our product).

For example, we use this library to control view models properties from server side. In our architecture, we have a server side that contains all the business logic and a client side which now uses WPF as presentation technology. We use the MVVM patterns so in the client side, there are a lot of viewmodel classes which are binded to a WPF view. It would be very nice if we could set a property of the viewmodel from server side which will affect the UI.

To achieve this, we use interfaces for the viewmodels. For example, we have a login viewmodel which has an ILoginViewModel interface. The attribute shows that it is implemented on client side. It has two properties (LoginName and Password) and one event (Login).

C#
[RemoteObjectRepositorySupportedType(DefaulRemoteObjectRepositoryComponent.Client)]
public interface ILoginViewModel
{
    string LoginName { get; set; }
    string Password { get; set; }

    event EventHandler TryLogin;
} 

Now, we should do the real implementation on the client side. We create a LoginViewModel class which implements this interface. The implementation is a little bit tricky because it implements the interface explicitly and implicitly. The implicitly implemented properties are binded to the WPF controls and it raises the RemotePropertyChanged event to notify the server side if the property value is changed. The explicit implementation of a property is called by the remote object repository and raises the PropertyChanged event which informs the WPF view that a property value is changed so refresh the UI.

For the event, we create a related command. We bind this command on the view and the command implementation is firing the event. So for example, if a button is clicked on the UI, it executes the command which raises the event and the remote object repository will raise that event on the other side.

C#
public class LoginViewModel  : ILoginViewModel
{
    private string loginName = null;
    public string LoginName
    {
        get
        {
            return loginName;
        }
        set
        {
            loginName = value;
            OnRemotePropertyChanged("LoginName", loginName);
        }
    }
    string ILoginViewController.LoginName
    {
        get
        {
            return loginName;
        }
        set
        {
            loginName = value;
            OnPropertyChanged("LoginName");
        }
    }

    private string password = null;
    public string Password
    {
        get
        {
            return password;
        }
        set
        {
            password = value;
            OnRemotePropertyChanged("Password", loginName);
        }
    }
    string ILoginViewController.Password
    {
        get
        {
            return password;
        }
        set
        {
            loginName = value;
            OnPropertyChanged("Password");
        }
    }

    public event EventHandler<RemotePropertyChangedEventArgs> RemotePropertyChanged;

    private void OnRemotePropertyChanged(string prop, object value)
    {
        if(RemotePropertyChanged != null)
        {
            RemotePropertyChanged(this, new RemotePropertyChangedEventArgs(prop, value));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string prop)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

    public ICommand LoginCommand
    {
        get;
        set;
    }
    private void LoginCommandImpl(object o)
    {
        if (Login != null)
        {
            Login(this, EventArgs.Empty);
        }
    }
    public event EventHandler Login;
} 

Because the interface is marked that the client side contains the implementation, the client side sends this type to the server side as a remotely implemented interface and the server's remote object repository will register the proxy creation method to the DI container. When we need a login user interface in a business process, then we retrieve a new instance of ILoginViewModel from the DI on the server side and subscribe to the login event to do the business logic when the user performed a login. (And we don’t know anything about the design if the user should click on a button or press a key.) In the event handler, we get the value of the LoginName and the Password. These values already contain the value given by the user so there is no network traffic getting the property values. So the server side code looks like this:

C#
IDynamicProxyJournalManager journal = DIContainer.Instance.GetInstance<IDynamicProxyJournalManager>();
using (journal.StartJournal())
{
    ILoginViewController loginVM = DIContainer.Instance.GetInstance<ILoginViewController();
    loginVM.Login += loginVM_Login;
}  

private void loginVM_Login(object sender, EventArgs e)
{
    ILoginViewController loginVM = sender as ILoginViewController; 
    string loginname = loginVM.LoginName;
    string password = loginVM.Password;

    businesslogic.DoLogin(loginname, password)
}   

Points of Interest

Our client side viewmodel classes have almost the same implementation and we try to prevent business logic in these classes, so we use TT script to generate these classes from the interface files. Create a DI container abstraction so everybody can use his own favourite implementation.

History

  • 28th January, 2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Nexogen
Hungary Hungary
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralEncapsulated Remoting Library Pin
stixoffire17-Apr-15 4:44
stixoffire17-Apr-15 4:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.