Disagree: Persist schema changes from file to database

Source: Internet
Author: User
Tags oauth

In the previous blog post, it was a whim to use file storage to achieve the persistence of OAuth refresh tokens. In this blog post, we will face the reality of changing the file store to database storage. Since the only constant in software development is the change itself, we take the initiative and change to verify that the code is designed to be adaptable.

The schema previously used for file storage is this:

    • Presentation Layer-webapi:cnblogsrefreshtokenprovider
    • Application Layer-Interface: Irefreshtokenservice
    • Application Layer-Implementation: Refreshtokenservice
    • Domain Layer-entity: Refreshtoken
    • Repository Layer-Interface: irefreshtokenrepository
    • Repository Layer-Implementation: Filestorage.refreshtokenrepository

The dependencies are like this:

    • Presentation Layer Cnblogsrefreshtokenprovider-Application layer interface Irefreshtokenservice + domain layer entity refreshtoken.
    • Application layer Implementation Refreshtokenservice-Repository Layer interface Irefreshtokenrepository + domain layer entity refreshtoken.
    • The implementation of the repository layer filestorage.refreshtokenrepository the entity Refreshtoken of the domain layer.

For such a layered architecture, it might seem simple to change the file store to a database store--simply based on database storage, using the appropriate ORM tool (such as EF), implementing the Irefreshtokenrepository interface, and then injecting it, without changing the 1 lines of code elsewhere.

When we leisurely to write the implementation of the Irefreshtokenrepository interface Database.refreshtokenrepository code, suddenly found something wrong.

The code that was previously based on file-store firestorage.refreshtokenrepository is so implemented (to simplify the problem, we only look at the implementation of the query section):

 public  class   refreshtokenrepository:irefreshtokenrepository{ private     List<refreshtoken> _refreshtokens;  public   Refreshtokenrepository () {//  ...     task<refreshtoken> FindByID (string   Id) { return  _refreshtokens . W Here (x = x.id = =    Id).    FirstOrDefault ();  }}

The same LINQ query code (the bold part of the following code) is now written on the Database.rrefreshtokenrepository implementation code based on the Entity Framework:

 Public classrefreshtokenrepository:irefreshtokenrepository{ Publicrefreshtokenrepository () {} Public AsyncTask<refreshtoken> FindByID (stringId) {using(varContext =NewOpenapidbcontext ()) {            returnContext. Set<refreshtoken>()                . Where (x = = X.id = = Id).        FirstOrDefault (); }    }}

It's just a repetition of 1 lines of code, but the more it looks, the more wrong. If you have more complex projects with many LINQ queries, there are multiple persistence methods and mock-up for unit tests, which will result in a lot of duplicate code.

When a change triggers repetitive code, it's certainly not the change itself, but the code itself-the code's design is problematic. Now repeat the code is in front of, now not solve, more when.

To solve the repetitive code problem, look at the differences before the same (repeating) code, and then look at the differences on the surface to find common ground, with different interface encapsulation. This is the code in the design of the method of differences (note: There is no such method, the writing of this blog post figment).

Back to the code above,. Where (x = x.id = = Id). FirstOrDefault (); The difference before is _refreshtokens and context. Set<refreshtoken> (), the type of the former is List<refreshtoken>, and the latter type is System.Data.Entity.DbSet, what are the similarities between the 2 differences?

In Visual Studio Press F12 key up, and finally found a common point is that Iqueryable--dbset implemented the IQueryable interface, The list can be converted to IQueryable (via the AsQueryable method). Now that we have found something in common, we can eliminate duplicate code by it and turn 2 refreshtokenrepository into 1 refreshtokenrepository.

 Public class refreshtokenrepository:irefreshtokenrepository{    private iqueryable<refreshtoken> _refreshtokens;      Public refreshtokenrepository ()    {    }    publicasync task<refreshtoken> FindByID (string  Id)    {        return _refreshtokens.where (x = x.id = = Id). FirstOrDefault ();    }}

The code above implements the same--from 2 different places to find common, but how to survive? How do you assign values to the _refreshtokens member variables in the above code based on different persisted storage methods? This has brought about the _refreshtokens of the problem of differences.

Did you ever think that there was one thing that was born to disagree and that it was--interface (Interface).

Then we introduce an interface to solve _refreshtokens assignment problem, this interface is called Iunitofwork Bar for the moment. The code for Iunitofwork is as follows:

 Public Interface Iunitofwork : idisposable{    IQueryablewhereclass;}

So refreshtokenrepository can assign values to _refreshtokens via the Iunitofwork interface:

 public  class   refreshtokenrepository:irefreshtokenrepository{ private     Iqueryable<refreshtoken> _refreshtokens;  public   Refreshtokenrepository ( Iunitofwork unitofwork) {_refreshtokens  = Unitofwork.set<refreshtoken>();  public  async  task< Refreshtoken> FindByID (string   Id) {return  _refreshtokens.where (x = x.id == Id) .    FirstOrDefault (); }}

Then we implement a filestorageunitofwork for the persistence of file storage:

 Public classfilestorageunitofwork:iunitofwork{ PublicIqueryable<tentity> set<tentity> ()whereTEntity:class    {        returnReadfromfile<tentity> (). Asqueryable<tentity>(); }    PrivateIlist<tentity> readfromfile<tentity>() {IList<TEntity> entities =NULL; varJsonfilepath = Hostingenvironment.mappath (string. Format ("~/app_data/{0}.json",typeof(TEntity))); if(File.exists (Jsonfilepath)) {varJSON =File.readalltext (Jsonfilepath); Entities= jsonconvert.deserializeobject<list<tentity>>(JSON); }        if(Entities = =NULL) entities =NewList<tentity>(); returnentities; }}

Then, for the persistence of database storage, a efunitofwork (the EF mapping configuration omitted) is implemented based on the Entity Framework:

 Public class Efunitofwork:dbcontext, iunitofwork{    publicnewwhereclass     {        returnbase. Set<tentity>();    }}

Finally, you want to use an IOC container (such as unity) to inject the corresponding unitofwork in any persistent way.

To use file storage, inject Filestorageunitofwork:

Container. Registertype<iunitofwork, filestorageunitofwork> (new httpcontextlifetimemanager<iunitofwork > ());

To use database storage, inject efunitofwork:

Container. Registertype<iunitofwork, efunitofwork> (new httpcontextlifetimemanager<iunitofwork> ());

This makes it easy to swap the persistence of the OAuth refresh token from the file store to the database store, from the database store to the file store. Or it's extremely easy to change to a NoSQL day.

Write so much nonsense, actually just for an interface of the powder full---iunitofwork. In order to keep the repository layer unchanged in the case of persistent changes, we introduced the Iunitofwork interface, which allows Repositroy to rely on iunitofwork and to encapsulate persistence in the iunitofwork implementation. This solves the repetitive code problem caused by the change of persistence mode. Actually realized again and again: small interface, large power.

Attached

The structure after the change is as follows:

    • Presentation Layer-webapi:cnblogsrefreshtokenprovider
    • Application Layer-Interface: Irefreshtokenservice
    • Application Layer-Implementation: Refreshtokenservice
    • Domain Layer-entity: Refreshtoken
    • Repository Layer-Interface: irefreshtokenrepository
    • Repository Layer-Implementation: Refreshtokenrepository
    • Unitofwork Layer-Interface: iunitofwork
    • Unitofwork Layer-Implementation: Filestorageunitofwork and Efunitofwork

Disagree: Persist schema changes from file to database

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.