Reinventing the wheel doesn't make sense. Especially not multiple times in the same project. That's why I came up with my minimalistic approach to model persistence in typescript: @royalzsoftware/royal-data-ts.
What does it do? (plain english)
At some point in our code, we want to save a domain object. Let's say we have a Post object, with a title
.
class Post {
constructor(public name: string, public authorName: string) {}
}
// create a new post
const myFirstPost = new Post("My first post", "Alexander Panov");
// create a repository with the builder
const inMemoryRepository = new CrudRepositoryBuilder<Post>().inMemory();
inMemoryRepository.create(myFirstPost());
That's it. With this, you already stored the Post
object.
Features
RxJs first
Don't struggle with promises. Take promises on steroids instead - out of the box.
Event Repository
This one came with the most recent changes and I think it's unbelievably helpful. In especially one code base I have been working on lately this was a huge pain.
The event repository keeps track of all the changes made within a session. Once the user wants to persist them, he/she simply calls eventRepository.applyTo(persistedRepository: CrudRepository<T>)
.
That way hard things and caveats won't matter anymore and the developer can continue working on the things he/she enjoys.
Ships with local storage and testing adapters
If one can take the backend implementation right away, one might not think about testing or swapping it. At least that's what I've seen very often.
With this system, you can swap out any repository with a InMemory
or LocalStorage
adapter.
Ships with extensible HTTP adapter
For the most part, this package will be used by applications that provide a REST HTTP API. The adapter for the basic operations comes included in the @royalzsoftware/royal-data-ts package.
If you're sticking to the rest specifications by heart, you can use the default RoutesConfiguration
. Most probably you will override them.
const httpUserRepository = new HttpRepositoryBuilder(httpClient,
(serializedPostData: any) => {
return new Post(serializedPostData.name, serializedPostData.authorName);
}
).withCustomRouteDefinitions({
create: '/posts',
getAll: '/posts',
getDetailsFor: (id: Id<Post>) => `/posts/${id.value}`,
update: (id: Id<Post>) => `/posts/${id.value}`,
delete: (id: Id<Post>) => `/posts/${id.value}`
});
No dependencies besides RxJS
You need to provide an HTTP client yourself, that's why there is currently no other dependency besides RxJS.
Angular example
In an angular client code we could use this to our advantage, because we are hiding a lot of redudant code, that is most likely to have bugs in it, away from the developer.
class Post {
constructor(public title: string, public authorName: string) {}
}
const REPO_DI_TOKEN = new InjectionToken<CrudRepository<Post>>('post_repo');
@Component({
selector: 'post-list',
template: `
<ul>
<li *ngFor="let post of posts">
{{post.model.title}} <button (click)="delete(post.id)">Delete</button>
</li>
<button (click)="persistChanges()">Save changes</button>
<input type="text" [(ngModel)]="postTitle"/>
<button (click)="createNewPostWithTitle()"></button>
</ul>
`
})
export class PostListComponent {
// UI binding for the text input
protected postTitle: any = "";
public posts: PersistedModel<Post>[] = [];
private _eventRepository: EventRepository<Post> = new EventRepository();
constructor(
/* don't wonder. I'm not going to dig further into the Crud Repo
initialization. You can find it in the @royalzsoftware/royal-data-ts
repository.
*/
@Inject(REPO_DI_TOKEN) private readonly _persistenceRepository: CrudRepository<Post>,
) {
this._initializeEventRepositoryWithPersistedData();
}
private _initializeEventRepositoryWithPersistedData() {
this._persistenceRepository.getAll({}).subscribe((persistedPosts) => {
this._eventRepository = new EventRepository<Post>(persistedPosts);
});
}
private _refreshPosts() {
this._eventRepository.getAll({}).subscribe((persistedPosts) => {
this.posts = persistedPosts;
});
}
protected delete(id: Id<Post>) {
this._eventRepository.delete(id).subscribe(() => this._refreshPosts());
}
protected createNewPostWithTitle() {
this._eventRepository.create(
new Post(this.postTitle, "Alexander Panov")
).subscribe(() => this._refreshPosts());
}
protected persistChanges() {
this._eventRepository.applyTo(
this._persistenceRepository
).subscribe();
}
}
Take a look at the code and see its beauty. The post does know absolutely nothing about how it persisted. It doesn't even know if it's persisted or what the fuck persistence is.
Conclusion
I hope you enjoyed this episode and may find a use case for the package. Leave feedback in the comments :)