Ddd Angularjs

Domain Driven Design in angularjs

For developing any software we need to have clear understanding of design philosophy which will be followed to solve the given problem.We do have myriads of options. Based on the requirements we choose the best fit.One of the design philosophy is Domain Driven design.

DDD

An object model of the domain that incorporates both behavior and data.When we talk about domain layer it is responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.

Example problem

Let us take an example of simple problem.
We have a user that can have one of the role USER,ADMIN or SUPERUSER
And We have list of jobs.
Now We need to create a dashboard. where in we have to display jobs.
We have following business rules:

  • ADMIN and SUPER USER can see all the jobs.
  • Normal USER can see only those jobs for which his education matches.
  • USER can apply for a job if it is not already applied by him and job is Active and it’s status is OPEN.
  • If job is already applied by USER,He can Un apply.
  • ADMIN and SUPER USER can’t apply jobs.

Firstly we will solve this problem with normal approach
Create angular module

angular.module('app', ['app.service']).controller('userCtrl', function ($scope,UserService,JobService) {
    $scope.user = UserService.getUser();
    $scope.jobs = JobService.getJobs();
    $scope.applyJob = function (job) {
        $scope.user.appliedJob.push(job);
    };
    $scope.unApplyJob = function (job) {
        $scope.user.appliedJob.splice($scope.user.appliedJob.indexOf(job), 1);
    };

});

In the above module we just define a userCtrl. And injected UserService and JobService. UserService provides the user.
and JobService provides the job listing.

We have two methods applyJob and unApplyJob in our controller
. using them user can apply and unapply for job.
We will now define our services


angular.module('app.service', []).service('UserService', function () {
this.getUser = function(){
var user= {
name: "Brij", role: "ADMIN", profile: {
email : "bpant@xebia.com", mobile: 1111111111,
education: {
degree: "MCA"
}
},
appliedJob : []
};
return user;
};
}).service('JobService', function () {
this.getJobs = function (){
var jobs = [
          {   id            : 1,
              active       : true,
              profile      : "MANAGER",
              qualification: ["MBA", "BBA"],
              status       : "OPEN"
          },
          {   id           : 2,
              active       : true,
              profile      : "RECRUITER",
              qualification: ["MBA", "BBA"],
              status       : "OPEN"
          },
          {   id           : 3,
              active       : true,
              profile      : "IT_HEAD",
              qualification: ["MCA", "MTECH"],
              status       : "OPEN"
          },
          {   id           : 4,
              active       : true,
              profile      : "SOFTWARE DEVELOPER",
              qualification: ["MCA", "BCA"],
              status       : "OPEN"
          } ,
          {   id           : 5,
              active       : true,
              profile      : "SOFTWARE TESTER",
              qualification: ["MCA", "BCA"],
              status       : "CLOSED"
          }
      ];
      return jobs;
  };

});

For creating Dashboard we will create index.html page

<!DOCTYPE html>

User Dashboard

Name:
Email:
Education:
Applied Jobs:
Profile Qualification Apply


```

Problem with this approach

You can see all our behavior is lied in the presentation layer. For displaying jobs we have applied all logic in our html file. moreover we have repeated our code to display jobs for normal user and for Admin/Superuser.
Similarly we have applied all the logic for displaying apply and unApply button in the html itself. We definitely should not have these logic in presentation layer.we can put these logics in our controller.But What if we want to bind this page with multiple controllers? We will have to write these logic in all those controllers.Situation will be more horrifying when our business demands some changes in the logic.Go find all the places and make those changes.

Time for refactoring

Let's think in different way.
We know in the current problem space we have two entities user and job. user's behavior changes based in its role.If it is Admin it has access to all jobs. But if it is normal user he can see only jobs which matches his education.
Job could be applied based on the behavior status.
So, now we will create Domain object for user and job. That will bind the behavior with them

 factory('User', function () {
            return function (user) {
                this.id = user.id;
                this.name=user.name;
                this.role = user.role;
                this.profile = user.profile;
                this.appliedJob = user.appliedJob;

                this.userViewableJobs = function (jobs) {
                    var userViewableJobs = [];
                    self =this;
                    if (this.role === 'ADMIN' || this.role === 'SUPER_USER') return jobs;
                    else {
                        angular.forEach(jobs, function (job) {
                            console.log('job '+job);
                            if (job.qualification.indexOf(self.profile.education.degree) !== -1) {
                                userViewableJobs.push(job);
                            }
                        })
                    }
                    return userViewableJobs;
                }
                this.canApplyForJob = function(job){
                  return job.canBeApplied() && !this.hasAlreadyAppliedForJob(job);
                }
                this.hasAlreadyAppliedForJob = function(job){
                    return this.appliedJob.indexOf(job) !=-1;
                }

                this.applyForJob = function (job) {
                    this.appliedJob.push(job);
                };
                this.unApplyJob = function (job) {
                    this.appliedJob.splice(this.appliedJob.indexOf(job), 1);
                };

            };
        })

We have bind following behavior with the User

userViewableJobs : based on role it will decide which jobs can be viewed by the user

canApplyForJob : it will check if job can be applied.

hasAlreadyAppliedForJob :it will check if job already applied by user

applyForJob : it will add job to the applied job list

unApplyJob : it will remove the job from the users listing

create Job domain object
```
factory('Job', function () {
return function (job) {
this.id = job.id;
this.active = job.active;
this.profile = job.profile;
this.qualification = job.qualification;
this.status = job.status;
this.canBeApplied = function(){
return this.active && this.status === 'OPEN';
}
};

Modify service to return User and Jobs as factory object instead of normal object

this.getUser = function(){
var user= { id:1,
name: "Brij", role: "ADMIN", profile: {
email : "bpant@xebia.com", mobile: 1111111111,
education: {
degree: "MCA"
}
},
appliedJob : []
};
return new User(user);
};


this.getJobs = function (){

    var jobs = [];
    angular.forEach([

        {   id            : 1,
            active       : true,
            profile      : "MANAGER",
            qualification: ["MBA", "BBA"],
            status       : "OPEN"
        },
        {   id           : 2,
            active       : true,
            profile      : "RECRUITER",
            qualification: ["MBA", "BBA"],
            status       : "OPEN"
        },
        {   id           : 3,
            active       : true,
            profile      : "IT_HEAD",
            qualification: ["MCA", "MTECH"],
            status       : "OPEN"
        },
        {   id           : 4,
            active       : true,
            profile      : "SOFTWARE DEVELOPER",
            qualification: ["MCA", "BCA"],
            status       : "OPEN"
        } ,
        {   id           : 5,
            active       : true,
            profile      : "SOFTWARE TESTER",
            qualification: ["MCA", "BCA"],
            status       : "CLOSED"
        }
    ],function(job){
       jobs.push(new Job(job))
    });
    return jobs;
};
remove applyJob and unApplyJob function from userCtrl as they are now part of User domain

controller('userCtrl', function ($scope,UserService,JobService) {

$scope.user = UserService.getUser();
$scope.jobs = JobService.getJobs();

})

Finally we will modify our index page

User Dashboard

Name:
Email:
Education:
Applied Jobs:
    <tr ng-repeat=" job in user.userViewableJobs(jobs)">
        <td></td>
        <td><span ng-repeat=" qualification in job.qualification "> </Span></td>
        <td>
            <span ng-if="user.canApplyForJob(job)">
                <button class="btn glyphicon glyphicon-ok" ng-click="user.applyForJob(job)" value="Apply">Apply</button></span> </span>
             <span ng-if="user.hasAlreadyAppliedForJob(job)">
             <button class="btn glyphicon glyphicon-remove" ng-click="user.unApplyJob(job)" value="UnApply">UnApply</button></span>
        </td>
    </tr>
    </tbody>
</table>
<hr>
Profile Qualification Apply

```
You can see now our code has become much cleaner,We need not to worry about the business logic in our presentation layer.Our controller has become free from any sort of responsibilities of modifying the model object.All the behavior related to the domain is abstracted from the outer world. You can use these domain objects any where in your application and can change them without any hiccup.

You can find out sample code here

Leave a Reply

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