Dependency Inversion Example

Adam Davies This article shows how you should structure your projects to satisfy the Dependency Inversion Principle. Please read the above linked pdf so you have a brief understanding of the importance of DIP. The technologies used are:
  • JSP for the view.
  • Spring MVC for server side REST services
  • JQuery AJAX for communication between the browser and the server
  • JSON for the data interchange between the client browser and the server
  • Maven as the build environment

Dependency Architecture

So we have a well structured project we need the following modules. These will be individual Maven projects.
  • website.war – Contains JSP pages and the Spring and web.xml configuration files.
  • website-services.jar – Contains the interface class that specifies the service we want provided by the business service layer. This also includes the corresponding POJOs we’ll work with.
  • domain.jar – This contains the implementation  of the services defined in web-services.jar and all other business logic.
The dependencies, as architectural modules, between these Maven projects is: Maven Module Dependencies

Directory Structure

Maven Project Structure

Maven Project Structure

The Parent pom.xml

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>uk.co.jeeni</groupId> <artifactId>parent</artifactId> <version>1.0</version> <packaging>pom</packaging> <name>Parent for full Website</name> <modules> <module>website</module> <module>website-services</module> <module>domain</module> </modules> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
 

website Project

/website/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>uk.co.jeeni</groupId> <artifactId>website</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>Web Application</name> <parent> <groupId>uk.co.jeeni</groupId> <artifactId>parent</artifactId> <version>1.0</version> </parent> <dependencies> <dependency> <groupId>uk.co.jeeni</groupId> <artifactId>website-services</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>uk.co.jeeni</groupId> <artifactId>domain</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <finalName>website</finalName> </build> </project>
Website Project Structure website project structure The above maven project structure is only concerned with the view. As such it only contains index.jsp, the web configuration web.xml fie and the Spring mvc-dispatcher-servlet.xml file. On a larger web app it would also contain css, JavaScript and the public image files. Although it would seem natural to include the service definition files, maven will not allow the domain module to have a war project as a dependency. Therefor we need to put the service interface files into a separate Maven jar project.
index.jsp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>

    <script>
        $(document).ready(function() {
            $("#addBtn").click(function(){
                postNewAddress();
            });

            $("#findBtn").click(function(){
                findAddress();
            });
        });

        function postNewAddress(){
            var data = "address=" + ($("#addrId").val()) + "&postcode=" + $("#pcId").val();
            postAjax("address/add", data, showResult);
        }

        function postAjax(url, dataString, callBackMethod) {
            $.ajax({type:"POST",
                dataType: "json",
                url: url,
                data: dataString,
                success: callBackMethod
            });
        }

        function showResult(data){
            $("#result").text(data.result);
        }

        function findAddress(){
            var url = "address/findByPostcode/" +  $("#queryPc").val();
            getAjax("GET", url, showFoundAddress);
        }

        function getAjax(type, url, callBackMethod) {
            $.ajax({type:"GET",
                dataType: "json",
                url: url,
                success: callBackMethod
            });
        }

        function showFoundAddress(address){
            $("#fddrId").val(address.address);
            $("#fpc").val(address.postcode);
        }

    </script>
    <style>
        label{
            width:75px;
            display:inline-block;
        }
        input, textarea {
            margin:2px;
            border: solid 1px #696969;
        }
    </style>

</head>
<body style="padding:10px;">

<div style="width:auto;float:left;">
    <h1>Add AddressXX</h1>
    <div style="padding:10px;border: 1px solid #696969;width:300px;">
        <label style="vertical-align:top" for="address">Address:</label><textarea id="addrId" name="address" rows="5"></textarea>
        <label for="postcode">Postcode:</label><input id="pcId" name="postcode"/></br>
        <button id="addBtn">Add</button></br></br>
        <div>Result: <span id="result">&nbsp;</span></div>
    </div>
</div>

<div style="width:auto;float:left;margin-left:20px;">
    <h1>Find Address</h1>
    <div style="padding:10px;border: 1px solid #696969;width:300px;">
        <label for="postcode">Postcode:</label><input id="queryPc" name="postcode"/><button id="findBtn">Find</button></br>
        <label style="vertical-align:top" for="fddrId">Address:</label><textarea id="fddrId" disabled="true" name="address" rows="5"></textarea>
        <label for="fpc">Postcode:</label><input id="fpc" disabled="true" name="postcode"/></br>
    </div>
</div>
</body>
</html>
web.xml
<web-app id="WebApp_ID" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Spring Web MVC Application</display-name>

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>
This file does nothing more than setup the Spring controller.
mvc-dispatcher-servlet.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <context:component-scan base-package="uk.co.jeeni.domain"/>

    <mvc:annotation-driven/>

</beans>
That’s all there is the the website module. All it does is contain the view code and the web-app configuration code.

website-services

Maven Project Structure website-services The project contains only an interface for the services which the web application requires from the domain/business tier. It is important the services are specified as part of the application tier module. This is so the lower-level domain module depends on the higher level application module and not the other way round. The application must be the driver for the design and implementation of the domain. Lets firstly look at the service specification interface.
PublicService.java
1
2
3
4
5
6
package uk.co.jeeni.application;

public interface PublicService {
    String addAddress(Address address);
    Address findByPostcode(String postcode);
}
Just a simple interface. Now lets look at the Address POJO
Address.java
1
2
3
4
5
6
7
8
package uk.co.jeeni.application;

public interface Address {
    String getAddress();
    void setAddress(String address);
    String getPostcode();
    void setPostcode(String postcode);
}
The intresting thing to note here is that even POJOs are defined as interfaces. This is because if we create another web application that defines an Address object, then they must both be interfaces. If we use concrete classes then the domain/code> will have a problem because it will have to cater for two types. In the design we are using here the domain can create a single Address class and represent both form via implementing both interfaces. Then application1 will only need to know about it's interface, and application2 will only need to know about it's.

The Domain Project

The domain project is structured like this: domain-project It’s only purpose is to provide implementations and business functionality to support the applications which use it. Anything else is unnecessary.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>uk.co.jeeni</groupId>
    <artifactId>domain</artifactId>
    <version>1.0.0</version>

    <packaging>jar</packaging>
    <name>Domain Implementation</name>

    <properties>
        <spring.version>3.2.1.RELEASE</spring.version>
    </properties>

    <parent>
        <groupId>uk.co.jeeni</groupId>
        <artifactId>parent</artifactId>
        <version>1.0</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>uk.co.jeeni</groupId>
            <artifactId>website-services</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

</project>
The dependency at line 29-33 is there so Spring will return JSON strings to our client. Now for the implementation classes.
PublicServiceImpl.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package uk.co.jeeni.domain;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import uk.co.jeeni.application.Address;
import uk.co.jeeni.application.PublicService;

import java.util.Hashtable;
import java.util.Map;

@Controller
@RequestMapping("/address")
public class PublicServiceImpl implements PublicService {

    private static Map<String, Address> data = new Hashtable<String, Address>();
    static {
        loadSomeData();
    }

    private static void loadSomeData(){
        data.put("BS21 7XS", new AddressImpl("Knowles Road", "BS21 7XS"));
        data.put("BS21 7SF", new AddressImpl("Hallam Road", "BS21 7SF"));
    }

    @Override
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @ResponseBody
    public String addAddress(@ModelAttribute("address")Address address) {
        String postcode = address.getPostcode();
        if(postcode != null && !data.containsKey(postcode)){
            data.put(postcode.toUpperCase(), address);
        }

        return "{\"result\":\"OK\"}";
    }

    @Override
    @RequestMapping(value="/findByPostcode/{postcode}", method = RequestMethod.GET)
    public @ResponseBody Address findByPostcode(@PathVariable String postcode){
        Address found = data.get(postcode.toUpperCase());
        return found;
    }

    @ModelAttribute
    public Address getAddress(){
        return new AddressImpl();
    }
}
Note: The address data is just a simple Map holding postcodes against Address objects. But most importantly, because the addAddress method take an Address as a parameter and this is an interface, Spring will throw a exception unless you tell it which concrete class to use. This is done by the annotated method at lines 44 to 47:
@ModelAttribute
public Address getAddress(){
  return new AddressImpl();
}
AddressImpl.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package uk.co.jeeni.domain;

import uk.co.jeeni.application.Address;

public class AddressImpl implements Address {
    private String address;
    private String postcode;

    public AddressImpl() {}

    public AddressImpl(String address, String postcode) {
        this.address = address;
        this.postcode = postcode;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }
}
Note: The default constructor which is required for Spring. domain-project Download this project here: WIPE_I8190

One thought on “Dependency Inversion Example

  1. Pingback: Dependency Inversion Example | Jeeni Software

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>