Layers and Exceptions

Proper layering is not about grouping classes but reducing complexity by managing dependencies. One thing that seems to bug a lot of people is how exceptions should be handled in a Layered architecture.

It’s interesting how this question relates to fundamental development principles like encapsulation, contracts and abstractions.

I see three types of exceptions in Layered Architectures:

  • Contract Exceptions: Exceptions that are part of the Layer’s contract therefore can be exposed to its clients
  • Internal Exceptions: Exceptions that were generated inside this Layer but are not part of its contract
  • Third Party Exceptions: Exceptions that were thrown by other Layers that this one uses.

Contract Exceptions

A Layer’s interface is composed by multiple operations, usually made available by one or more Façades. Those operations have contracts and among other requirements a contract defines what the client must expect when calling one of those operations. Those expectations include the exceptions that the operations may throw.

What kind of exceptions should be in a contract? Keep in mind that a Layer should abstract its internals from other Layers. Leaking implementation details isn’t completely avoidable but must be reduced whenever possible. Do not let exceptions that are not relevant to other Layers (i.e. exceptions they’re not interested in or can’t handle) leak into the contract.

When an exception is part of the contract exposed to the Upper Layer it is ok to just throw it.

As an example, consider the code below.

 public void addUserToGroup(User userToBeAdded, String groupName) throws InexistentGroupException {
        Group group = groupRepository.findByName(groupName);
        if(group == null){
            throw new InexistentGroupException("Group [" + groupName  + "] does not exist");
        }
        group.add(userToBeAdded);
    }
 

The InexistentGroupException is part of the contract for the operation. If this exception happens inside the method it is ok –though not required- to throw it back to the Upper Layer.

It’s probably worth mentioning that method signatures do not define contracts. In languages like Java the signature has some information about the contract but not all the relevant parts of it. In this particular case, a signature may exclude unchecked exceptions but those are still part of the contract.

Internal Exceptions

All operations performed by a computer program can result in some kind of exception. It is very unlikely that all exceptions that can be thrown in your code will be part of the contract.

If the exception was generated inside this Layer and is not part of the contract you may still want to throw an exception as the result of this computation. What you have to do is to map the internal exception to a contract exception. The example below shows how this could be implemented:

    public void addUserToGroup(User userToBeAdded, String groupName) throws InexistentGroupException, GroupModificationException {
                try{

                    Group group = groupRepository.findByName(groupName);

                    if(group == null){
                        throw new InexistentGroupException("Group [" + groupName + "] does not exist");
                }
                    group.add(userToBeAdded);

                }catch(RepositoryException e){
                    throw new GroupModificationException("Error when modifying group", e);
                }
    }
 

One technique that can be really useful here is to let the internal exception extend a contract exception. In this scenario no mapping is required and the Upper Layer still have no knowledge of our internals. Example:

class RepositoryException extends GroupModificationException{
    //...
}

//...

public void addUserToGroup(User userToBeAdded, String groupName) throws InexistentGroupException, GroupModificationException {
                    Group group = groupRepository.findByName(groupName);

                    if(group == null){
                        throw new InexistentGroupException("Group [" + groupName + "] does not exist");
                    }
                    group.add(userToBeAdded);
    }
}

Just keep in mind that inheritance is something really strong and not just a trick to fool the compiler.

Third Party Exceptions

Layers use other Layers and can call other systems or subsystems. Those operations can result in exceptions being thrown and once more it is very unlikely that those are part of this Layer’s contract.

As stated previously a Layer encapsulates its implementation. The Layers and subsystems used by it are part of what gets encapsulated. If your client Layers are not supposed to know what resources your Layer uses behind the curtains why should you expose exceptions you got from them?

Talking about exposing, this is not that different from internal exceptions. The main difference is probably in that you won’t be able to let third-party exceptions implement your exceptions so you will always have to map them.

public class GroupRepository{
    public Group findByName(String nameToLookFor) throws RepositoryException{
        Group group;

        try{
        group groupDAO.findById(nameToLookFor);
        } catch(SQLException e){
            throw new RepositoryException("Problems in the persistence...", e);
	}
        return group;
    }
}

Logging

So, who logs exceptions in Layered Architectures? There are multiple possibilities here but I’d recommend that the first Layer to find out about the exception –because it created it or received from other subsystem- should log it and the Upper Layers should not care about that.

13 Responses to “Layers and Exceptions”


  1. 1 Antonio Carlos Zegunis Filho (tucaz) Dec 5th, 2008 at 12:43 am

    Hi Phillip!

    Nice post!

    You stated that first layer who gets to know an exception should log it, but it causes some strange sensation at me. If we do that, its possible that every layer on the system is going to care about logging exceptions. Presentation layer will log exceptions fired there, Model will do that as well data access layer.

    What do you think about it?

    I think since i’m going to let every layer know about an exception (mapped or not), the ultimate layer only should take care of logging through some logging service.

  2. 2 Timothy High Dec 5th, 2008 at 1:51 am

    You know, one interesting factor in this, that has been a problem to our own “Remodularization” project, is that wrapping an internal or third-party exception in a layer-contractual exception does NOT always guarantee encapsulation.

    Case in point: we have server-side code, and we have GUI code. And we like to keep them separate as much as possible (in separate modules). There are some classes that must be shared between the two: our “domain” or “model” classes, which represent the business logic and data structures exposed by the server-side “services”.

    So, GUI code on one side, server code on the other, and some shared binaries for shared logic. Where this DOESN’T work is exactly with Exceptions, at least when using the most common of wrapping the lower Exception in a higher-level one, as you exemplified. In rare cases, we’ve seen that exception get propagated to the client, and throw a NoClassDefFoundError because some code on the GUI tried to access the getCause() exception. The cause, of course, was a server-side exception, and its class definition was only deployed on the server.

    Of course, this doesn’t violate anything you’re saying - it’s just an example of how even if you wrap a lower-level exception, you’re still slightly breaking encapsulation. There are two solutions to this issue: NEVER wrap the causing exception (instead, copy the message and throw a totally new one), or explicitly break encapsulation by providing the client with the binaries (either by copying them to the client JAR, or by moving them to the shared “domain” modules).

  3. 3 AC de Souza Dec 5th, 2008 at 6:30 am

    “[…]because some code on the GUI tried to access the getCause() exception.[…]”

    I’m not sure that I understood, but seems the problem is the GUI code who tries to break the encapsulation doing getCause().

    The client should ONLY worry about exceptions the contract exposed, isn’t?

    But if you agree with this, what was the purpose of encapsulate a third part exception in the contract exception?

    [],
    AC

  4. 4 Phillip Calçado "Shoes" Dec 5th, 2008 at 10:05 am

    @Antonio Carlos Zegunis

    I don’t understand how multiple Layers would log the exception. The first one to receive it, the one who generates the exception or receives it from a subsystem or library (*not from other Layer*) will log that.

    About logging in the last Layer, this is helpful as stack traces can give you the full path. Problem is that although it is very easy to know who is the first Layer it is hard to realise who is the last Layer. If a Layer knows that it is the last one it knows too much about the system, Layers should be atomic and independent.

    @Tim

    Besides the point raised by AC about not getting into the exception it feels like what you are having is the classic DTO/local objects problem. In this case “mapping” may mean detaching it from the server.

  5. 5 AC de Souza Dec 6th, 2008 at 5:38 am

    @Tim and @Philip
    I was thinking about “exception encapsulation” and concludes that:
    - If you’re mapping one, or a group, of exception there is no reason to encapsulate the original.

    You should log the problem when it happened and throw an exception which your client can understand and handle. So the original exception has no meaning to the client, if he can’t handle it.

    But if your client could handle, why don’t you throw the original exception instead some pseudo-abstraction?

    And if your client can handle, doesn’t him doing more than he supossed to?

    [],
    AC

  6. 6 Timothy High Dec 10th, 2008 at 4:59 am

    Responding to @AC,
    There are a number of reasons why you might want to get down to the cause of an Exception, not all of them valid from a purely theoretical standpoint:

    1) The client has some sort of monitoring / error reporting facility that sends any server exceptions to an admin via email, via a monitoring agent, or some other means.

    2) The client may want to automatically respond to certain kinds of server-side exceptions. We have defined a sub-type of Exception called “Retriable” (say, when a server is overloaded, or has run out of DB connections). When an exception of this type is thrown, the client can check if the “cause” is of this type, and can retry the request without bothering the user.

    3) A developer may be manually debugging or running a profiling tool that wants to have a look at the innards of the Exception. It would be a bummer if the act of inspecting the object causes it to blow up in your face.

    @Phillip,
    Right, that’s basically the point I was trying to make. Just a reminder that wrapping an Exception, while usually helpful, may actually be breaking encapsulation and causing problems in unexpected ways.

  7. 7 AC de Souza Dec 10th, 2008 at 10:25 pm

    @Tim

    First of all, I would like to sorry my argumenting. But, I REALLY intrested in opinions about this topic.

    1) This monitoring agent should’t be in the server? Or, maybe, you could worry when you map a server exception to a client exception that your client monitoring agent understand, or using an Exception’s Code.

    2) This could be done with an Exception’s Code, in the Client Exception, instead a Server Exception. Something the HTTP code 502: Service Temporarily Overloaded. As java.sql.SQLException do: venderCode.

    3) In this case I need to know the exactaly developer’s intend(what is he looking for?) to propouse a solution. But, if its a IDE debug, de developer always can put a breakpoint in the caller method :)

    [],
    AC

  8. 8 Chuck Dec 24th, 2008 at 3:50 am

    “What you have to do is to map the internal exception to a contract exception.”

    With respect to how the exceptions are handled and logged, why does it matter if I convert the internal exception to a contract exception?

    I’ve always applied this rule on a case by case basis. I’ve seen other people suggest the ‘do it all the time’ approach, but I’ve never seen a good explanation as to why. I suspect this has more to do with code ownership and who’s consuming your code.

    To me it seems more important to do this when exceptions move across subsystems or trust boundaries (e.g. exception shielding), and less important when moving across layers.

  9. 9 Phillip Calçado "Shoes" Dec 24th, 2008 at 7:34 am

    Chuck,

    It is important because if you don’t map the original exception to something that is specified in the contract you just broke it. The contract defines what the client Layer must expect, all exceptions that can be thrown by a Layer should be defined in the contract.

    Unless, of course, that you don’t use contracts between Layers. This is other topic but I’d recommend that you limit the number of possible exceptions anyway, otherwise the client Layer would have to be able to deal with pretty much any possible situation that could happen.

  10. 10 Chuck Dec 24th, 2008 at 11:33 am

    “The contract defines what the client Layer must expect, all exceptions that can be thrown by a Layer should be defined in the contract.”

    But what benefit does the client have knowing this? It seems like only a small amount of exceptions that might get thrown can be recovered from, so what can the client really do with this information?

    “otherwise the client Layer would have to be able to deal with pretty much any possible situation that could happen”

    I’d say this is always the case and client layers should be designed with this in mind. You even mentioned this with your reference to Joel’s leaky abstraction article.

    I’m still not convinced there’s a significant benefit (maybe an example or reference would help). I understand your point with contracts, and it sounds nice, however I’m not convinced that you can often map every exception. With this in mind, it doesn’t seem practical to map some, knowing others might leak.

  11. 11 Phillip Calçado "Shoes" Jan 1st, 2009 at 2:16 pm

    The whole point about contracts is to establish a protocol between client and server. This protocol provides the desired encapsulation as it leaks very few information about how the server is implemented. If you think that a given exception should be known to the client just make it part of the contract.

    In the other hand there are times where the contract has to be broken and maybe that’s the kind of exception you are talking about. In that case bubbling up exceptions is fine but this is a broken contract, an uncommon situation often created by some failure in libraries or infrastructure your system uses.

    If your client Layer has to handle and know all possible states of the server Layer then what’s the point in using Layers in the first place? Just group your objects into packages for some cohesion and let them talk. The reason client Layers should not know about how the underlying Layer is implemented is encapsulation. If you require the client to know all possible states of the server Layer you have high coupling between those, what’s generally something undesirable.

    What Joel’s article says is that all abstractions leak and I agree with that. It does not say that we should encourage leaky abstractions; it says that we have to keep in our minds that it is not feasible to design an abstraction that won’t leak.

    Now I’m not saying that you have to use Layers of even any high level abstractions, but once you decide to use that it is often good to maximise cohesion and minimise coupling. Grouping classes will give you the former but it is extremely hard to get the latter without a good API for your abstractions; and a good API has a good contract.

    I’ll probably post examples in an upcoming post.

  12. 12 Rubem Azenha Jan 3rd, 2009 at 5:37 am

    Shoes, when you told that the first layer who noticied the exception should log it, you mean in your example:

    public class GroupRepository{
    public Group findByName(String nameToLookFor) throws RepositoryException{
    Group group;

    try{
    group groupDAO.findById(nameToLookFor);
    } catch(SQLException e){
    throw new RepositoryException(”Problems in the persistence…”, e);
    }
    return group;
    }
    }

    The catch clause should log the exception?

    } catch(SQLException e){
    magicLogger.log(e)
    throw new RepositoryException(”Problems in the persistence…”, e);
    }

  1. 1 links for 2008-12-05 « Object neo = neo Object Pingback on Dec 6th, 2008 at 2:31 pm





Creative Commons License

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.