Connector Development v4.1 Service Framework

The UNIFYBroker Framework contains all assemblies required to develop any sub-component in UNIFYBroker. For the purposes of this document, we will only focus on Connectors and Agents, but new Transformations and Schema field types can be written as well.

Schema Provider

A schema provider allows a connector to "provide" zero-to-many schemas back to UNIFYBroker.

Each schema provider has a user-friendly name, a unique factory name and a method to retrieve the schema. The retrieval can be dynamic (in which case the connector asks the target system), or static (in which case the connector has a constant definition of the schema). See the sample connector implementation for more details.

Entities

Entities are the underlying object that connectors work with. An entity is a flat object with key value pairs corresponding to the data in the target system. The surface of an entity is described by the connector schema. For instance, a Person connector may have:

var schema = new EntitySchemaConfiguration
{
    {"ID", new EntitySchemaFieldDefinitionConfiguration("ID", true, false, false, "int")},
    {"FirstName", new EntitySchemaFieldDefinitionConfiguration("FirstName", false,false,false, "string")},
    {"LastName", new EntitySchemaFieldDefinitionConfiguration("LastName", false, false, false, "string")},
    {"TerminationDate", new EntitySchemaFieldDefinitionConfiguration("TerminationDate", false, false, false, "timestamp")}
};

An entity for that person connector would have a unique ID, First Name, Last Name and a Termination Date.

Entities can be populated and accessed through the SetValue and GetValue operations. For example, if an entity were provided from the Person connector, the Termination Date could be retrieved through the GetValue method:

DateTime time = personEntity.GetValue<DateValue>("TerminationDate");

And consequently the termination date could be set through the SetValue method:

personEntity.SetValue<DateValue>("TerminationDate", DateTime.UtcNow);

The above is a manual process of setting the value, but the Schema itself can be used to generically set values on an entity. If given access to the schema, the Termination date could be set as such:

IEntitySchemaFieldDefinition terminationDateField;
if (_Schema.TryGetValue("TerminationDate", out terminationDateField))
{
    IValue terminationDate = terminationDateField.CreateValue("22/05/1990 12:05:33");
    personEntity.SetValue("TerminationDate", terminationDate);
}

The point being, that you can use the schema fields themselves to create the respective entity IValue objects given any particular input.

Agent interface

Unlike connectors, agents do not do anything of note from the perspective of UNIFYBroker. The only requirement of an Agent is that it supply a TestConnection method; such that UNIFYBroker can validate the configuration as provided and alert where necessary.

The implementation of an agent is not generic, in the same way that talking to target system is not generic. An agent may describe a WCF endpoint, or a SQL Server database, or a JSON file, or a web-server etc.

The agent should only act as a container for the information required to connect to the system it describes. For example, an agent describing a database might only expose a method to return an IDbConnection that other connectors can use to connect with.

Connector interfaces

As mentioned earlier, connectors manage the identity data in the target systems through a series of operations. These are the Add / Update and Delete entities operations, as well as the Import All and Import Changes reading operations. Although they can implement these operations, the only one that must be implemented is the Import All operation. The following examples will be using a hypothetical service contract with the following interface:

public interface Service
{
    void RegisterPerson(PersonBusinessObject person);
    void Update(PersonBusinessObject person);
    void Remove(int id);
    PersonBusinessObject Get(int id);
    PersonBusinessObject[] GetAll();
    int[] ChangedPersons();
}

IMultiKeyedReadingConnector

All connectors must at least implement this interface. The IMultiKeyedReadingConnector interface exposes the GetAllEntities (Import All) method, which is used to baseline a connector. As its name suggests, this operation gets all entities from the target system.

public IEnumerable<IConnectorEntity> GetAllEntities(IStoredValueCollection storedValueState)
{
    foreach (PersonBusinessObject person in _Service.GetAll())
    {
        IConnectorEntity entity = new ConnectorEntity();
        entity.SetValue<IntegerValue>("ID", person.ID);
        entity.SetValue<StringValue>("FirstName", person.FirstName);
        entity.SetValue<StringValue>("LastName", person.LastName);
        entity.SetValue<DateValue>("TerminationDate", person.TerminationDate);
        yield return entity;
    }
}

Of particular note is the usage of the yield directive. The architecture of UNIFYBroker has been designed for environments with millions of records being managed. In these types of environments, paging the data from target systems is almost always a prerequisite for success. UNIFYBroker pages through any data given to it, and this is through the IEnumerable interface. It is recommended, where possible, to page and yield results to take full advantage of C# lazy evaluation.

IMultiKeyedAddingConnector

This interface exposes the AddEntity and AddEntities methods, which gives a connector the capability to handle outgoing adds. Both of these operations give one-to-many IConnectorEntity instances to add to the target Identity Store.

public void AddEntity(IConnectorEntity entity)
{
    _Service.RegisterPerson(
        new PersonBusinessObject
        {
            ID = entity.GetValue<IntegerValue>("ID"),
            FirstName = entity.GetValue<StringValue>("FirstName"),
            LastName = entity.GetValue<StringValue>("LastName"),
            TerminationDate = entity.GetValue<DateValue>("TerminationDate")
        });
}

AddEntity should be employed if the target system can only support one-by-one exports, otherwise AddEntities should be used to enforce batch adds. With the above scenario, the AddEntities implementation would then resolve to:

public void AddEntities(IEnumerable<IConnectorEntity> entities)
{
    foreach (IConnectorEntity entity in entities)
        AddEntity(entity);
}

IMultiKeyedUpdatingConnector

This interface exposes the UpdateEntity and UpdateEntities methods, which give a connector the capability to handle outgoing updates. Both of these operations give one-to-many IConnectorEntity instances to update in the target Identity Store.

public void UpdateEntity(IConnectorEntity entity)
{
    _Service.Update(
        new PersonBusinessObject
        {
            ID = entity.GetValue<IntegerValue>("ID"),
            FirstName = entity.GetValue<StringValue>("FirstName"),
            LastName = entity.GetValue<StringValue>("LastName"),
            TerminationDate = entity.GetValue<DateValue>("TerminationDate")
        });
}

UpdateEntity should be employed if the target system can only support one-by-one exports, otherwise UpdateEntities should be used to enforce batch updates. With the above scenario, the UpdateEntities would resolve to:

public void UpdateEntities(IEnumerable<IConnectorEntity> entities)
{
    foreach (IConnectorEntity entity in entities)
        UpdateEntity(entity);
}

IMultiKeyedDeletingConnector

This interface exposes the DeleteEntity, DeleteEntities and DeleteAllEntities methods, which give a connector the capability to handle outgoing deletes. Both of these operations give one-to-many IConnectorEntity instances to delete from the target Identity Store.

public void DeleteEntity(MultiKeyValue entityId)
{
    _Service.Remove((IntegerValue)entityId.KeyValues[0]);
}
public void DeleteEntities(IEnumerable<MultiKeyValue> entityIds)
{
    foreach (MultiKeyValue multiKeyValue in entityIds)
        DeleteEntity(multiKeyValue);
}

The delete entity methods are different to the other export operations, in the sense that they provide the Keys of the entities that have been deleted. The key values can be accessed in the MultiKeyValue.KeyValues array.

In the above implementation, there is only one key (ID), so the key value is at index 0. The position of values correspond to their position in the connector schema.

public void DeleteAllEntities()
{
    foreach (PersonBusinessObject person in _Service.GetAll())
        _Service.Remove(person.ID);
}

The DeleteAllEntities method is vastly different to all other export operations, in the sense that nothing is provided. It is up to the implementation to clear everything from the target system. In the above implementation, all values are requested, and cleared one-by-one.

IMultiKeyedEntityPollingConnector

This interface exposes the PollEntityChanges method, which gives a connector the capability to handle incoming changes from the target system. This method returns the actual values of any entities that have been added or updated. This operation does not support deletes.

public IEnumerable<IConnectorEntity> PollEntityChanges(IStoredValueCollection changeState)
{
    int[] changedKeys = _Service.ChangedPersons();
    foreach (int changedKey in changedKeys)
    {
        PersonBusinessObject person = _Service.Get(changedKey);
        IConnectorEntity entity = new ConnectorEntity();
        entity.SetValue<IntegerValue>("ID", person.ID);
        entity.SetValue<StringValue>("FirstName", person.FirstName);
        entity.SetValue<StringValue>("LastName", person.LastName);
        entity.SetValue<DateValue>("TerminationDate", person.TerminationDate);
        yield return entity;
    }
}

IMultiKeyedIdPollingConnector

This interface exposes the GetEntity, GetEntities and PollIdChanges methods, which give a connector the capability to handle incoming changes from the target system. This method works in two steps:

  1. First by requesting the key values of the entities that have changed through PollIdChanges.
  2. Then requesting the entities themselves through the GetEntities method, using the keys retrieved in the first step.

Unlike the IMultiKeyedEntityPollingConnector method, this operation type supports deletes. A delete is registered when the GetEntities operation does not return one of the changed keys.

public IEnumerable<MultiKeyValue> PollIdChanges(IStoredValueCollection changeState)
{
    foreach (int changedId in _Service.ChangedPersons())
        yield return new MultiKeyValue(new IValue[]{ (IntegerValue)changedId });
}
 
public IConnectorEntity GetEntity(MultiKeyValue entityId)
{
    PersonBusinessObject person = _Service.Get((IntegerValue)entityId.KeyValues[0]);
    IConnectorEntity entity = new ConnectorEntity();
    entity.SetValue<IntegerValue>("ID", person.ID);
    entity.SetValue<StringValue>("FirstName", person.FirstName);
    entity.SetValue<StringValue>("LastName", person.LastName);
    entity.SetValue<DateValue>("TerminationDate", person.TerminationDate);
    return entity;
}
public IEnumerable<IConnectorEntity> GetEntities(IEnumerable<MultiKeyValue> entityIds)
{
    foreach (MultiKeyValue id in entityIds)
        yield return GetEntity(id);
}

IMultiKeyedPasswordSynchronisationConnector

This interface exposes the ChangePassword and SavePassword methods, which give a connector the capability to handle standard Password synchronization events.

Is this article helpful for you?