3

(I asked a similar question which answers focused on compile time instead of runtime. Instead of adding the runtime requirements to the other question and invalidating the good answers, I decided to ask a new question that focuses on runtime communication)

Scenario

A web application defines a user backend interface IUserBackend with the methods

  • getUser(uid)
  • createUser(uid)
  • deleteUser(uid)
  • setPassword(uid, password)
  • ...

Different user backends (e.g. LDAP, SQL,...) implement this interface but not every backend can do everything. For example a concrete SQL server does not allow (permissions to DELETE) this user backend to delete users. Or the administrator did not setup the SQL queries which are used to create users.

The user backend needs to react to this during runtime and communicate to the web application whether or not it can delete users with the current configuration.

Known solution

I have seen a solution where the IUserInterface has a implementedActions method that returns a integer which is the result of bitwise ORs of the actions bitwise ANDed with the requested actions:

function implementedActions(requestedActions) {
    return (bool)(
        ACTION_GET_USER
        | ACTION_CREATE_USER
        | ACTION_DELTE_USER
        | ACTION_SET_PASSWORD
        ) & requestedActions)
}

Where

  • ACTION_GET_USER = 1
  • ACTION_CREATE_USER = 2
  • ACTION_DELETE_USER = 4
  • ACTION_SET_PASSWORD = 8
  • .... = 16
  • .... = 32

etc.

So the web application sets a bitmask with what it needs and implementedActions() answers with a boolean whether it supports them.

Opinion

These bit operations to me look like relics from the C age, not necessarily easy to understand in terms of clean code.

Question

What is a modern (better?) pattern for the class to communicate the subset of the interfaces methods it implements for a given configuration during runtime? Or is the "bit operation method" from above still the best practice?

(In case it matters: PHP, although I am looking for a general solution for OO languages)

1
  • 1
    HATEOAS fits your description, though it's only a specific example that pertains to REST, and it's not comprehensive in the sense of being universally applicable to all API's. Apr 27, 2018 at 22:20

4 Answers 4

2

Let the caller ask. Pseudocode:

ACTION = {CREATE, UPDATE, DELETE}

class FooGateway {
   function supports(action) {
      return {ACTION.CREATE, ACTION.UPDATE}.contains(action)
   }

   // The interface requires this, so fail if the caller attempts.
   function delete() {
      error('DELETE not supported')
   }
   // etc
}

So the caller is like

gateway = findGatewayFor('Foo')
if gateway != null and gateway.supports(ACTION.DELETE) {
   gateway.delete(something)
   return "Deleted that!"
} else {
   return "Oops, got no way to delete that."
}
1

As a variation of this, each action that can be performed on a user can be encapsulated by an object and an interface that wraps up the "is supported" check and the execution of the action. Couple that with a factory that implements a generic interface and you end up with a safer, object oriented way of doing this.

First the interface for the "action factory":

interface IUserActionFactory {
    function getUser() : IGetUserAction;
    function changePassword() : IChangePasswordAction;
    function deleteUser() : IDeleteUserAction;
}

class LdapUserActionFactory implements IUserActionFactory {
    function getUser() {
        return new LdapGetUserAction();
    }

    function changePassword() {
        return new LdapChangePasswordAction();
    }

    function deleteUser() {
        return new LdapDeleteUserAction();
    }
}

Nothing spectacular. These methods return objects supporting an interface. Without further adu, the interfaces:

interface IChangePasswordAction {
    function isSupported();
    function execute($username, $oldPassword, $newPassword);
}

interface IGetUserAction {
    function isSupported();
    function execute($username);
}

interface IDeleteUserAction {
    function isSupported();
    function execute($username);
}

Now all you need to do is use them (more on the implementation later):

class UserService {
    private $actionFactory;

    public function __construct(IUserActionFactory $actionFactory) {
        $this->actionFactory = $actionFactory;
    }

    public function getUser($username) {
        $getUserAction = $this->actionFactory->getUser();

        if ($getUserAction->isSupported()) {
            return $getUserAction->execute($username);
        }
        else {
            return null;
        }
    }

    public function changePassword($username, $oldPassword, $newPassword) {
        $changePasswordAction = $this->actionFactory->changePassword();

        if ($changePasswordAction->isSupported()) {
            return $changePasswordAction->execute($username, $oldPassword, $newPassword);
        }
        else {
            return false;
        }
    }

    public function deleteUser($username) {
        $deleteUserAction = $this->actionFactory->deleteUser();

        if ($deleteUserAction->isSupported()) {
            return $deleteUserAction->execute($username);
        }
        else {
            return false;
        }
    }
}

The UserService just needs an IUserActionFactory object as a constructor argument. Then performing each action becomes a predictable check and call methodology.

In the classes that implement each action you can specialize in the particular user service, for instance LDAP:

// Generic exception for when an action cannot be executed
class ActionNotSupportedException extends Exception { }

class LdapGetUserAction implements IGetUserAction {
    public function isSupported() {
        return true;
    }

    public function execute($username) {
        $user = // get from LDAP

        return $user;
    }   
}

class LdapChangePasswordAction implements IChangePasswordAction {
    function isSupported() {
        return true;
    }

    function execute($username, $oldPassword, $newPassword) {
        if (/* password gets changed */) {
            return true;
        }
        else {
            return false;
        }
    }
}

class LdapDeleteUserAction implements IDeleteUserAction {
    public function isSupported() {
        return false;
    }

    public function execute($username) {
        throw new ActionNotSupportedException("Cannot delete users via LDAP");
    }
}

I suppose you could name the "execute" method something specific, like "executeGetUser" or "getUser". With unique method names you could have 1 class that implements all of the interfaces.

Now if an exception gets thrown it should be because, say, LDAP goes down and the server is unreachable, or you try to change a password for a user that doesn't exist, instead of trying to interpret exceptions as "this thing is not supported."

And to use it:

$userService = new UserService(new LdapUserActionFactory());

if ($userService->deleteUser($POST['username'])) {
    echo "User deleted";
}
else {
    echo "You cannot delete this user";
}

Switching your user management is easy once you have the implementing classes:

$userService = new UserService(new ActiveDirectoryUserActionFactory());

if ($userService->deleteUser($POST['username'])) {
    echo "User deleted";
}
else {
    echo "You cannot delete this user";
}

If you call a method on the $userService and it throws an ActionNotSupportedException then you know you have a defect to fix in UserFactory. It shouldn't be throwing that exception.

1

I am not familiar with the PHP syntaxes but perhaps you could take advantage of lambdas, if PHP allows you define an interface as a set of 'getters' returning the functions to call. (ie. readonly lambda variables of specific signatures)

Each concrete implementation would in its constructor set the actual lambda variables to either a specific function, or to a lambda passed into the constructor that specifies the action to take when the function is not available. Further, constructor can accept a lambda for itself (if PHP allows the signature to be specified), to inform the client in advance if need be.

This way the caller does not need to enumerate or check what is available, it can tell the constructor what to do when a function is not available. This allows you to both reactively invoke something on the web application side whenever an unavailable message is called ("Deleting this user is unavailable"), or actively do something at the time the implementation is constructed to pass an object back to the web application in advance of any user actions (eg: hiding the delete user button). Static lambdas might be helpful to do this at program initialization.

1

You started on the wrong foot. Check out the I in SOLID: the interface segregation principle.

An interface should be small, that is define only one capability. Getting something and deleting something are different capabilities in your problem domain so you want separate interfaces for those. And once you have them your problem will be gone.

Just use the instanceOf type operator.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.