Using Data Mapper in Angular 2
By Nikola Shahpazov
In this post I'm going to show you a way of separating your concerns when it comes to dealing with the network communication of your application. First, lets start with a simple diagram.
Data Mapper is an architectural pattern for separating the business logic of your domain model from the network communication logic.
A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in memory data representation (the domain layer).
You can read more about it here.OK, but since we are interested in the practical use, lets start with the contract.
import User from '../models/User';
import { Observable } from 'rxjs/Observable';
interface IMapper {
get<T>(id: number): Observable<T>;
query<T>(): Observable<T>;
save<T>(record: T): Observable<T>;
create<T>(): Observable<T>;
remove(record: T): Observable<T>;
}
We define a simple interface following the diagram and describing basic network communication operations done with a model. All operations return an observable which works with a particular domain object Type.
import IMapper from '../interfaces/IMapper';
import User from '../models/User';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { USER_URI } from '../constants/uris';
@Injectable();
class UserMapper implements IMapper {
constructor(private http: Http) {}
get(id: number): Observable<User> {
return this.http.get(`${USER_URI}/${id}`)
.then(res => new User(res.json()));
}
query(): Observable<User[]> {
return this.http.get(USER_URI)
.map(res => res.json().map(data => new User(data)));
}
save(user: User): Observable<User> {
const { id } = user;
return this.http.put(`${USER_URI}/${id}`, user)
.map(res => new User(res.json()));
}
create(data): Observable<User> {
return this.http.post(USER_URI, data)
.then(res => new User(res.data));
}
remove(user: User): Observable<boolean> {
return this.http.delete(`${USER_URI}/${user.id}`)
.then(res => res.status === 200);
}
}
As you can see we are just implementing the interface according to our domain object needs. We are using ramda functional programming library for expressing ourselves in a more declarative way. An example domain object might be something like this:
class User {
private id: number;
private prop1: string;
private prop2: string;
private prop3: string;
constructor(data) {
Object.assign(this, data);
}
purchase(item: IShopItem) {
// purchase business logic
}
sell(item: IShopItem) {
// sell business logic
}
}
Conclusion
Using the UserMapper and the User class, we have a very clear separation of concerns. The User domain object doesn't know anything about the network communication, all it cares about is it's own business logic.