Currently, I am integrating clean architecture in my latest project and facing challenges with repositories, data sources, and terminology.
My aim is to test my useCases
using an in-memory repository as I am only concerned about the business logic at this point. However, I find it difficult to understand the terminology used and would appreciate some clarification on this matter.
To start off, I have defined a base DataSource
as follows:
export interface DataSource<T> {
create(request: any): Promise<T>
// other methods omitted for simplicity
}
Further, I have outlined the interface for Patient
like so:
export interface PatientRepository {
createPatient(patient: Patient): Promise<void>
// other methods omitted for simplicity
}
For production purposes, I plan to utilize Sequelize
, whereas for unit testing, I will employ a local array of entities
.
In the event where I need to implement the PatientRepository
, how should I proceed?
Option 1
I could develop a PatientSequelizeRepository
that adheres to the methods specified in my contract and extends an abstract
SequelizeRepository</code which implements the <code>DataSource
contract. Additionally, I could also establish a PatientInMemoryRepository
implementing the same methods but extending an abstract InMemoryRepository</code which fulfills the <code>DataSource
contract. Subsequently, I would import the PatientInMemoryRepository
within my use-case
, and include the PatientSequelizeRepository
in the app's composition.
Option 2
Alternatively, I may create a single implementation of the PatientRepository
and pass a data-source
parameter in the constructor since the methods for the PatientRepository
remain consistent. I could introduce a generic InMemoryDataSource
and SequelizeDataSource
, both abiding by the DataSource
contract. By default, I would construct the repository using the Sequelize
implementation while providing flexibility to switch to alternative data sources if required.
export class PatientRepositoryImpl implements PatientRepository {
constructor(dataSource: DataSource<Patient>) { }
async createPatient(patient: Patient) {
return this.dataSource.create(patient)
}
}
export const patientRepository = new PatientRepositoryImpl(new SequelizeDataSource())
The naming conventions here pose a dilemma for me. While inserting a SequelizeDataSource
into the constructor seems logical, should I create a PatientSequelizeDataSource
, assign it to the constructor, and repeat the process for PatientInMemoryDataSource
? Is there an optimal course of action? Could there perhaps be an unexplored third option I have overlooked?