DDD理論學(xué)習(xí)系列(12)-- 倉儲(chǔ)

DDD理論學(xué)習(xí)系列——案例及目錄


1. 引言

DDD中的Repository,主要有兩種翻譯:資源庫倉儲(chǔ),本文取倉儲(chǔ)之譯。

說到倉儲(chǔ),我們肯定就想到了倉庫,倉庫一般用來存放貨物,而倉庫一般由倉庫管理員來管理。當(dāng)工廠生產(chǎn)了一批貨物時(shí),只需交給倉庫管理員即可,他負(fù)責(zé)貨物的堆放;當(dāng)需要發(fā)貨的時(shí)候,倉庫管理員負(fù)責(zé)從倉庫中撿貨進(jìn)行貨物出庫處理。當(dāng)需要庫存盤點(diǎn)時(shí),倉庫管理員負(fù)責(zé)核實(shí)貨物狀態(tài)和庫存。換句話說,倉庫管理員負(fù)責(zé)了貨物的出入庫管理。通過倉庫管理員這個(gè)角色,保證了倉庫和工廠的獨(dú)立性,工廠只需要負(fù)責(zé)生產(chǎn)即可,而至于貨物如何存放工廠無需關(guān)注。

而我們要講的倉儲(chǔ)就類似于倉庫管理員,只不過它負(fù)責(zé)的不再是貨物的管理,而是聚合的管理,倉儲(chǔ)介于領(lǐng)域模型和數(shù)據(jù)模型之間,主要用于聚合的持久化和檢索。它隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進(jìn)行持久化。

2. DDD中的倉儲(chǔ)

2.1. 倉儲(chǔ)的集合特性

倉儲(chǔ)代表一個(gè)聚合的集合,其行為與.Net集合一樣,倉儲(chǔ)用來存儲(chǔ)和刪除聚合,但同時(shí)提供針對(duì)聚合的顯式查詢以及匯總。

2.2. 倉儲(chǔ)與數(shù)據(jù)訪問層的區(qū)別

  1. 倉儲(chǔ)限定了只能通過聚合根來持久化和檢索領(lǐng)域?qū)ο螅源_保所有改動(dòng)和不變性由聚合處理。
  2. 倉儲(chǔ)通過隱藏聚合持久化和檢索的底層技術(shù)實(shí)現(xiàn)領(lǐng)域?qū)拥牡某志没療o關(guān)性(即領(lǐng)域?qū)硬恍枰廊绾纬志没I(lǐng)域?qū)ο螅?/li>
  3. 倉儲(chǔ)在數(shù)據(jù)模型和領(lǐng)域模型定義了一個(gè)邊界。

2.3. 倉儲(chǔ)舉例

下面我們首先來看一個(gè)簡(jiǎn)單倉儲(chǔ)的定義:

namespace DomainModel
{
 public interface ICustomerRepository
 {
 Customer FindBy(Guid id);
 void Add(Customer customer);
 void Remove(Customer customer);
 }
}

通常來說,倉儲(chǔ)由應(yīng)用服務(wù)層調(diào)用。倉儲(chǔ)定義應(yīng)用服務(wù)執(zhí)行業(yè)務(wù)用例時(shí)需要的所有的數(shù)據(jù)訪問方法。而倉儲(chǔ)的實(shí)現(xiàn)通常位于基礎(chǔ)架構(gòu)層,由持久化框架來支撐。以下的倉儲(chǔ)實(shí)現(xiàn)是借助于ORM框架Nhibernate的ISession接口,它扮演一個(gè)的網(wǎng)關(guān)角色,負(fù)責(zé)領(lǐng)域模型和數(shù)據(jù)模型的映射。

namespace Infrastructure.Persistence {
    public class CustomerRepository : ICustomerRepository {
        private ISession _session;
        public CustomerRepository (ISession session) {
            _session = session;
        }
        public IEnumerable<Customer> FindBy (Guid id)
            return _session.Load<Order> (id);
        }

        public void Add (Customer customer) {
            _session.Save (customer);
        }

        public void Remove (Customer customer) {
            _session.Delete (customer);
        }
    }
}

從上面我們可以看出,將領(lǐng)域模型的持久化轉(zhuǎn)移到基礎(chǔ)設(shè)施層,隱藏了領(lǐng)域模型的技術(shù)復(fù)雜性,從而使領(lǐng)域?qū)ο竽軌驅(qū)W⒂跇I(yè)務(wù)概念和邏輯。

2.4. 倉儲(chǔ)的誤解

倉儲(chǔ)也存在很多誤解,許多人認(rèn)為其是不必要的抽象。當(dāng)應(yīng)用于簡(jiǎn)單的領(lǐng)域模型時(shí),可以直接使用持久化框架來進(jìn)行數(shù)據(jù)訪問。然而當(dāng)對(duì)復(fù)雜的領(lǐng)域模型進(jìn)行建模時(shí),倉儲(chǔ)是模型的擴(kuò)展,它表明聚合檢索的意圖,可以對(duì)領(lǐng)域模型進(jìn)行有意義的讀寫,而不是一個(gè)技術(shù)框架。

也有很多人認(rèn)為倉儲(chǔ)是一種反模式,因?yàn)槠潆[藏了基礎(chǔ)持久化框架的功能。而恰巧這正是倉儲(chǔ)的要點(diǎn)。基礎(chǔ)持久化框架提供了開放的接口用于對(duì)數(shù)據(jù)模型的查找和修改,而倉儲(chǔ)通過使用定義的命名查詢方法來限制對(duì)聚合的訪問。通過使查詢顯式化,就更容易調(diào)整查詢,且更重要的是倉儲(chǔ)明確了查詢的意圖,便于領(lǐng)域?qū)<依斫狻Ee個(gè)例子:我們?cè)趥}儲(chǔ)中定義了一個(gè)方法GetAllActiveUsers()與sql語句select * from users where isactive = 1var users =db.Users.Where(u=>u.IsActive ==1)相比,很明顯倉儲(chǔ)的方法命名就能讓我們明白了查詢的意圖:查詢所有處于Active狀態(tài)的用戶。除了查詢,倉儲(chǔ)僅暴露必要的持久化方法而不是提供所有的CURD方法。

2.5. 倉儲(chǔ)的要點(diǎn)

倉儲(chǔ)的要點(diǎn)并不是使代碼更容易測(cè)試,也不是為了便于切換底層的持久化存儲(chǔ)方式。當(dāng)然,在某種程度上,這也的確是倉儲(chǔ)所帶來的利好。倉儲(chǔ)的要點(diǎn)是保持你的領(lǐng)域模型和技術(shù)持久化框架的獨(dú)立性,這樣你的領(lǐng)域模型可以隔離來自底層持久化技術(shù)的影響。如果沒有倉儲(chǔ)這一層,你的持久化基礎(chǔ)設(shè)施可能會(huì)泄露到領(lǐng)域模型中,并影響領(lǐng)域模型完整性和最終一致性。

3. 領(lǐng)域模型 VS 數(shù)據(jù)模型

如果選擇關(guān)系型數(shù)據(jù)庫作為持久化存儲(chǔ),我們可以借助于ORM框架來實(shí)現(xiàn)領(lǐng)域模型和數(shù)據(jù)模型之間的映射和持久化操作。

而ORM又是什么呢?

按照文章開頭中的例子,如果倉儲(chǔ)對(duì)應(yīng)倉庫管理員的角色,那ORM就相當(dāng)于倉庫機(jī)器人,而倉庫就相當(dāng)于數(shù)據(jù)庫。為了方便不同商品的歸類存放,對(duì)倉庫進(jìn)行分區(qū),分區(qū)就相當(dāng)于數(shù)據(jù)表。當(dāng)公司接到一筆訂單做發(fā)貨處理時(shí),銷售員將發(fā)貨通知單告知倉庫管理員,倉庫管理員再分配ORM機(jī)器人進(jìn)行撿貨。很顯然,ORM機(jī)器人必須能夠識(shí)別發(fā)貨通知單,將發(fā)貨通知單中的商品對(duì)應(yīng)到倉庫中存儲(chǔ)的貨物。這里面發(fā)貨通知單就相當(dāng)于領(lǐng)域模型,而倉庫中存儲(chǔ)的貨物就屬于數(shù)據(jù)模型。

相信基于上面的比喻,我們對(duì)ORM有了基本的認(rèn)識(shí)。ORM,全稱是Object Relational Mapping,對(duì)象關(guān)系映射。ORM的前提是,將對(duì)象的屬性映射到數(shù)據(jù)庫字段,將對(duì)象之間的引用映射到數(shù)據(jù)庫表的關(guān)系。換句話說,ORM負(fù)責(zé)將代碼中定義的對(duì)象和關(guān)系映射到數(shù)據(jù)庫的表結(jié)構(gòu)中去,并在進(jìn)行數(shù)據(jù)訪問時(shí)再將表數(shù)據(jù)映射到代碼中定義的對(duì)象,借助ORM我們不需要去手動(dòng)寫SQL語句就可以完成數(shù)據(jù)的增刪改查。ORM僅僅抽象了關(guān)系數(shù)據(jù)模型,它只是以面向?qū)ο蟮姆绞絹肀硎緮?shù)據(jù)模型,以方便我們?cè)诖a中輕松地處理數(shù)據(jù)。

下面我們來探討一下數(shù)據(jù)模型與領(lǐng)域模型的異同。關(guān)系數(shù)據(jù)庫中的數(shù)據(jù)模型,它由表和列組成,它只是簡(jiǎn)單的存儲(chǔ)結(jié)構(gòu),用于保存領(lǐng)域模型某個(gè)時(shí)間點(diǎn)的狀態(tài)。數(shù)據(jù)模型可以分散在幾個(gè)表甚至幾個(gè)數(shù)據(jù)庫中。此外,可以使用多種形式的持久化存儲(chǔ),例如文件、web服務(wù)器、關(guān)系數(shù)據(jù)庫或NoSQL。領(lǐng)域模型是對(duì)問題域的抽象,具有豐富的語言和行為,由實(shí)體和值對(duì)象組成。對(duì)于一些領(lǐng)域模型,可能與數(shù)據(jù)模型相似,甚至相同,但在概念上它們是非常不同的。ORM與領(lǐng)域模型無關(guān)。倉儲(chǔ)的作用就是將領(lǐng)域模型與數(shù)據(jù)模型分開,而不是讓它們模糊成一個(gè)模型。ORM不是倉儲(chǔ),但是倉儲(chǔ)可以使用ORM來持久化領(lǐng)域?qū)ο蟮臓顟B(tài)。

如果你的領(lǐng)域模型與你的數(shù)據(jù)模型類似,ORM可以直接映射領(lǐng)域模型到數(shù)據(jù)存儲(chǔ),否則,則需要對(duì)ORM進(jìn)行額外的映射配置。

4. 倉儲(chǔ)的定義和實(shí)現(xiàn)

上面也提到過,我們一般在領(lǐng)域?qū)佣x倉儲(chǔ)接口,在基礎(chǔ)設(shè)施層實(shí)現(xiàn)倉儲(chǔ),以隔離領(lǐng)域模型和數(shù)據(jù)模型。

4.1. 倉儲(chǔ)方法需明確

倉儲(chǔ)是原則上是領(lǐng)域模型與持久化存儲(chǔ)之間明確的契約,倉儲(chǔ)定義的接口方法不僅僅是CURD方法。它是領(lǐng)域模型的擴(kuò)展,并以領(lǐng)域?qū)<宜斫獾男g(shù)語編寫。倉儲(chǔ)接口的定義應(yīng)該根據(jù)應(yīng)用程序的用例需求來創(chuàng)建,而不是從類似CURD的數(shù)據(jù)訪問角度來構(gòu)建。

我們來看一段代碼:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatMatch (Query query);
        IEnumerable<Customer> FindAllThatMatch (String hql);
        void Add (Customer customer);
    }
}

以上倉儲(chǔ)定義了一個(gè)FindAllThatMatch方法以支持客戶端以任何方式查詢領(lǐng)域?qū)ο蟆_@個(gè)方法的設(shè)計(jì)思想無可置否,靈活且可以擴(kuò)展,但是它并沒有明確的表明查詢的意圖,我們就失去了對(duì)查詢的控制。為了真正了解如何使用這些方法,開發(fā)人員需要跟蹤相關(guān)調(diào)用堆棧,才能知悉方法的意圖,更別說出現(xiàn)性能問題時(shí)如何著手優(yōu)化了。因?yàn)閭}儲(chǔ)定義的接口方法過于寬泛且不具體,它模糊了領(lǐng)域的的概念,所以定義這樣的一個(gè)接口方法是無意義的。

我們可以如下改造:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatAreDeactivated ();
        IEnumerable<Customer> FindAllThatAreOverAllowedCredit ();
        void Add (Customer customer);
    }
}

通過以上改造,我們通過方法的命名來明確查詢的意圖,符合通用語言的規(guī)范。

4.2. 泛型倉儲(chǔ)

在實(shí)踐中我們可能會(huì)發(fā)現(xiàn),為每一個(gè)聚合定義一個(gè)倉儲(chǔ)會(huì)導(dǎo)致重復(fù)代碼,因?yàn)榇蟛糠值臄?shù)據(jù)操作都是類似的。為了代碼重用,泛型倉儲(chǔ)就應(yīng)時(shí)而生。

泛型倉儲(chǔ)舉例:

namespace DomainModel {
    public interface IRepository<T> where T : EntityBase {
        T GetById (int id);
        IEnumerable<T> List ();
        IEnumerable<T> List (Expression<Func<T, bool>> predicate);
        void Add (T entity);
        void Delete (T entity);
        void Edit (T entity);
    }

    public abstract class EntityBase {
        public int Id { get; protected set; }
    }
}

泛型倉儲(chǔ)實(shí)現(xiàn):

namespace Infrastructure.Persistence {
    public class Repository<T> : IRepository<T> where T : EntityBase {
        private readonly ApplicationDbContext _dbContext;
        public Repository (ApplicationDbContext dbContext) {
            _dbContext = dbContext;
        }
        public virtual T GetById (int id) {
            return _dbContext.Set<T> ().Find (id);
        }

        public virtual IEnumerable<T> List () {
            return _dbContext.Set<T> ().AsEnumerable ();
        }

        public virtual IEnumerable<T> List (Expression<Func<T, bool>> predicate) {
            return _dbContext.Set<T> ()
                .Where (predicate)
                .AsEnumerable ();
        }

        public void Insert (T entity) {
            _dbContext.Set<T> ().Add (entity);
            _dbContext.SaveChanges ();
        }

        public void Update (T entity) {
            _dbContext.Entry (entity).State = EntityState.Modified;
            _dbContext.SaveChanges ();
        }

        public void Delete (T entity) {
            _dbContext.Set<T> ().Remove (entity);
            _dbContext.SaveChanges ();
        }
    }
}

通過定義泛型倉儲(chǔ)和默認(rèn)的實(shí)現(xiàn),很大程度上進(jìn)行了代碼重用。但是,嘗試將泛型倉儲(chǔ)應(yīng)用所有倉儲(chǔ)并不是一個(gè)好的主意。對(duì)于簡(jiǎn)單的聚合我們可以直接使用泛型倉儲(chǔ)來簡(jiǎn)化代碼。但對(duì)于復(fù)雜的聚合,泛型倉儲(chǔ)可能就會(huì)不太適合,如果基于泛型倉儲(chǔ)的方法進(jìn)行數(shù)據(jù)訪問,就會(huì)模糊對(duì)聚合的訪問意圖。

對(duì)于復(fù)雜的聚合,我們可以重新定義:

namespace DomainModel {
    public interface ICustomerRepository {
        Customer FindBy (Guid id);
        IEnumerable<Customer> FindAllThatAreDeactivated ();
        void Add (Customer customer);
    }
}

在實(shí)現(xiàn)時(shí),我們可以引用泛型倉儲(chǔ)來避免代碼重復(fù)。

namespace Infrastructure.Persistence {
    public class CustomerRepository : ICustomerRepository {
        private IRepository<Customer> _customersRepository;
        public Customers (IRepository<Customer> customersRepository) {
            _customersRepository = customersRepository;
        }
        // ....
        public IEnumerable<Customer> FindAllThatAreDeactivated () {
            _customersRepository.List(c => c.IsActive == false);
        }
        public void Add (Customer customer) {
            _customersRepository.Add (customer);
        }
    }
}

通過這種方式,我們即明確了查詢了意圖,又簡(jiǎn)化了代碼。

4.3. IQueryable Vs IEnumerable

在定義倉儲(chǔ)方法的返回值時(shí),我們可能會(huì)比較疑惑,是應(yīng)該直接返回?cái)?shù)據(jù)(IEnumerable)還是返回查詢(IQueryable)以便進(jìn)行進(jìn)一步的細(xì)化查詢?返回IEnumerable會(huì)比較安全,但IQueryable提供了更好的靈活性。事實(shí)上,如果使用IQueryable作為返回值,我們僅提供一種讀取數(shù)據(jù)的方法即可進(jìn)行各種查詢。
但是這種方式就會(huì)引入一個(gè)問題,就是業(yè)務(wù)邏輯會(huì)滲透到應(yīng)用層中去,并出現(xiàn)大量重復(fù)。比如,在實(shí)體中我們一般使用IsActiveIsDeleted屬性來表示軟刪除,而一旦實(shí)體中的某條數(shù)據(jù)被刪除,那么UI中基本不會(huì)再顯示這條數(shù)據(jù),那對(duì)于實(shí)體的查詢都需要包含類似Where(c=> c.IsActive)的linq表達(dá)式。對(duì)于這種問題,我們最好在倉儲(chǔ)中的方法中,比如List()或者ListActive()做默認(rèn)處理,而不是在應(yīng)用服務(wù)層每次去指定查詢條件。
但具體是返回 IQueryable還是IEnumerable每個(gè)人的看法不一,具體可參考Repository 返回 IQueryable?還是 IEnumerable?

5. 事務(wù)管理和工作單元

事務(wù)管理主要是應(yīng)用服務(wù)層的關(guān)注點(diǎn)。然而,因?yàn)閭}儲(chǔ)和事務(wù)管理緊密相關(guān)的。倉儲(chǔ)僅關(guān)注單一聚合的管理,而一個(gè)業(yè)務(wù)用例可能會(huì)涉及到多種的聚合。

事務(wù)管理由UOW(Unit of Work)處理。UOW模式的作用是在業(yè)務(wù)用例的操作中跟蹤聚合的所有更改。一旦發(fā)生了更改,UOW就使用事務(wù)來協(xié)調(diào)持久化存儲(chǔ)。為了確保數(shù)據(jù)的完整性,如果提交數(shù)據(jù)失敗,則會(huì)回滾所有更改,以確保數(shù)據(jù)保持有效狀態(tài)。

而關(guān)于UOW又是一個(gè)復(fù)雜的話題,我們后續(xù)再講。

6. 倉儲(chǔ)的反模式(注意事項(xiàng))

  1. 不要支持臨時(shí)查詢(ad hoc query)
    倉儲(chǔ)不應(yīng)該開放擴(kuò)展,不要為了支持多種形式的查詢,定義比較寬泛的查詢方法,它不僅不能明確表達(dá)倉儲(chǔ)查詢的意圖,更可能會(huì)導(dǎo)致查詢性能。

  2. 延遲加載是一種設(shè)計(jì)臭味
    聚合應(yīng)圍繞不變性構(gòu)建,并包含所有必需的屬性去支持不變性。 因此,當(dāng)加載聚合時(shí),要么加載所有,要么一個(gè)也不加載。 如果您有一個(gè)關(guān)系數(shù)據(jù)庫并且正在使用ORM作為數(shù)據(jù)模型,那么您可能能夠延遲加載一些領(lǐng)域?qū)ο髮傩裕@樣就可以推遲加載不需要的聚合部分。但是,這樣做的問題是,如果您只能部分加載聚合,可能會(huì)導(dǎo)致您的聚合邊界錯(cuò)誤。

  3. 不要使用聚合來實(shí)現(xiàn)報(bào)表需求
    報(bào)表可能會(huì)涉及到多個(gè)類型的聚合,而倉儲(chǔ)是處理單一聚合的。另外倉儲(chǔ)是基于事務(wù)的,可能會(huì)導(dǎo)致報(bào)表的性能問題。

7. 總結(jié)

  1. 倉儲(chǔ)作為領(lǐng)域模型和數(shù)據(jù)模型的中介,它負(fù)責(zé)映射領(lǐng)域模型到持久化存儲(chǔ)。
  2. 倉儲(chǔ)實(shí)現(xiàn)了透明持久化,即領(lǐng)域?qū)硬恍枰P(guān)注領(lǐng)域?qū)ο笕绾纬志没?/li>
  3. 倉儲(chǔ)是一個(gè)契約,而不是數(shù)據(jù)訪問層。它明確表明聚合所必需的數(shù)據(jù)操作。
  4. ORM框架不是倉儲(chǔ)。倉儲(chǔ)是一種架構(gòu)模式。ORM用來以面向?qū)ο蟮姆绞絹肀硎緮?shù)據(jù)模型。倉儲(chǔ)使用ORM來協(xié)調(diào)領(lǐng)域模型和數(shù)據(jù)模型。
  5. 倉儲(chǔ)適用于具有豐富領(lǐng)域模型的限界上下文。對(duì)于沒有復(fù)雜業(yè)務(wù)邏輯的簡(jiǎn)單限界上下文,直接使用持久化框架即可。
  6. 使用UOW進(jìn)行事務(wù)管理。UOW負(fù)責(zé)跟蹤對(duì)象的狀態(tài),倉儲(chǔ)在UOW協(xié)調(diào)的事務(wù)中進(jìn)行實(shí)際的持久化工作。
  7. 倉儲(chǔ)用于管理單個(gè)聚合,它不應(yīng)該控制事務(wù)。

參考資料:
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的實(shí)踐經(jīng)驗(yàn)分享之持久化透明
Repository Pattern--A data persistence abstraction
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的實(shí)踐經(jīng)驗(yàn)分享之ORM的思考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,360評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評(píng)論 0 286
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,559評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,742評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,981評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評(píng)論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,354評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,707評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容