DentallApp is a web application with chatbot for appointment management, reminders and sending appointment cancellation messages for the dental office called World Dental CO.
The maintainer of this repository is Dave Roman.
This project has been improved too much since its first alpha version.
- Important
- Motivations
- Technologies used
- Software Engineering
- Installation
- Plugin configuration
- Credentials
- Validate identity documents
- Configure languages
- Diagrams
- Direct Line API
- EF Core Migrations
- Running tests
- Contribution
This application was developed as a degree project for the University of Guayaquil, however, it is not ready to run in a production environment. All requirements for this project were obtained through interviews with the owner dentist of World Dental CO.
In the end, this project was never deployed in that dental office for personal reasons of the authors. However, it was decided to publish the source code of this application so that the community can use it for learning purposes (learn from it or even improve it).
I have continued to maintain this project because I have been experimenting with plugin-based architecture and I love it.
I have not found any .NET project that has applied this architectural pattern and I don't mean a sample project, but one that solves a problem. For that reason I decided to apply it in this project, I'm sure many will find it useful as knowledge.
Another of my reasons is that what I learn about software engineering, I like to share with the community. That's why I have been inspired to improve it.
The best way to learn things is to do projects.
- .NET CLI
- Visual Studio 2022
- vscode
- Docker
- Postman
- InDirectLine
- MariaDB
- HeidiSQL
- BotFramework-Emulator
- GitHub Actions
- Git
- draw.io
- ASP.NET Core
- Microsoft Bot Framework
- AdaptiveCards
- Twilio
- SendGrid
- SendGrid.Extensions.DependencyInjection
- libphonenumber-csharp
- Quartz.Net
- Quartz.Extensions.Hosting
- Swashbuckle.AspNetCore
- Scrutor
- efcore
- Pomelo.EntityFrameworkCore.MySql
- EFCore.NamingConventions
- linq2db.EntityFrameworkCore
- EntityFramework.Exceptions
- EFCore.CustomQueryPreprocessor
- DelegateDecompiler
- Dapper
- Microsoft.VisualStudio.Threading.Analyzers
- Microsoft.AspNetCore.Authentication.JwtBearer
- Microsoft.IdentityModel.Tokens
- System.IdentityModel.Tokens.Jwt
- BCrypt.Net-Next
- Scriban
- itext7.pdfhtml
- File.TypeChecker
- FluentValidation
- FluentValidation.DependencyInjectionExtensions
- NUnit
- FluentAssertions
- JustMock
- coverlet.msbuild
- Respawn
- Microsoft.AspNetCore.Mvc.Testing
- Microsoft.Bot.Builder.Testing
- DotEnv.Core
- YeSql.Net
- SimpleResults
- CPlugin.Net
- CPlugin.Net.Attributes
- CopySqlFilesToOutputDirectory
- CopyPluginsToPublishDirectory
Software engineering concepts have been applied in this project:
- Vertical Slice Architecture
- CQRS
- Plugin-based architecture
- Interface-based programming
- Modular programming
- Dependency injection
- Operation Result Pattern
- Guard Clause
- Fail Fast
- Open-closed principle
- Acyclic dependencies principle
- Explicit dependencies
- Separation of concerns
Additional references:
- Software principles and design
- Plugin Architecture Pattern in C# by Alvaro Montoya
- Plugin Architecture Design Pattern by Nick Cosentino
- Plugin Architecture In C# For Improved Software Design by Nick Cosentino
- Vertical Slice Architecture in ASP.NET Core by Swapnil Meshram
- Vertical Slice Architecture in ASP.NET Core by Code Maze
To run this application, it is recommended to install Docker, it is much easier to install the app with Docker.
- Clone the repository with this command.
git clone https://github.com/DentallApp/back-end.git
- Change directory.
cd back-end
- Copy the contents of
.env.example
to.env
.
cp .env.example .env
# On Windows use the "xcopy" command.
- You must specify the time zone for the Docker container. This is necessary for the calculation of available hours for a medical appointment to return consistent results. The logical thing to do would be to choose the time zone in which the dental clinic is located (which in this case would be America/Guayaquil).
echo -e '\nTZ=America/Guayaquil' >> .env
- Build the image and initiate services.
docker compose up --build -d
- Access the application with this URL.
http://localhost:5000/swagger
- If you wish to test the chatbot, you can do so with the test client. Access this URL.
https://dentallapp.github.io/webchat-client
NOTE: Twilio.WhatsApp and SendGrid (these are plugins) are not loaded by default. So the app will use a fake provider that uses a console logger (useful for a development environment).
By default only two plugins are loaded:
Plugin.ChatBot.dll
Plugin.AppointmentReminders.dll
You can add other plugins by modifying the PLUGINS key from the .env file:
PLUGINS="
Plugin.ChatBot.dll
Plugin.AppointmentReminders.dll
Plugin.Twilio.WhatsApp.dll
Plugin.SendGrid.dll
"
Of course, for this to work, you will need to create an account on Twilio and SendGrid, generate the necessary credentials and then add it to the .env file.
You can also remove all plugins. The host application will work without any problems.
The following table shows the default credentials for authentication from the application.
Username | Password |
---|---|
basic_user@hotmail.com | 123456 |
secretary@hotmail.com | 123456 |
dentist@hotmail.com | 123456 |
admin@hotmail.com | 123456 |
superadmin@hotmail.com | 123456 |
Use this route for authentication:
POST - /api/user/login
Request body:
{
"userName": "basic_user@hotmail.com",
"password": "123456"
}
To validate identity documents, it depends largely on the country where the dental office is located. At the moment, we can only validate identity documents registered in Ecuador.
You can enable it from the configuration file, e.g.
PLUGINS="
Plugin.IdentityDocument.Ecuador.dll
"
In case there is no plugin loaded to validate the identity document, the host application will use a fake provider called FakeIdentityDocument.
It was decided to implement the logic to validate identity documents from a plugin, because it is flexible, since it allows to change the implementation without having to modify the source code of the host application.
This project uses resource files to store response messages in different languages. If you want to add a new language, you must modify the Languages section of appsettings.json
.
"Languages": [
"es",
"en",
"fr"
]
More details
Overview of each component:
- Host Application. Contains everything needed to run the application. It represents the entry point of the application.
This layer performs other tasks such as:
- Load plugins from a configuration file (.env) using the library called CPlugin.Net.
- Finds the types that implement the interfaces shared between the host application and the plugins to create instances of those types.
- Add services to the service collection, register middleware, load SQL files, load the .env file, among other things.
- Shared Layer. It contains common classes and interfaces between many components. This layer has aspects (additional parts) that are not related to the main processes of the application.
- This layer contains the interfaces that allow communication between the host application and the plugins.
- This layer does not contain the implementation of a functional requirement.
- It contains other things such as:
- Extension classes
- Exception classes
- Classes mapped to the database schema (entities)
- Data models
- Value objects
- Objects that represent error and success messages
- Constants
- Settings objects
- Language resources
- Common validation rules
- Repository and service interfaces
- Core Layer. Contains the main processes (essential features) of the application.
- Each feature represents a functional requirement of what the app should do.
- A feature contains the minimum code to execute a functional requirement.
- The purpose of grouping related elements of a feature is to increase cohesion.
- By convention, each feature module contains a:
- Controller
- Request/Response
- Validator
- Use case class (has the logic of the functional requirement)
- Infrastructure Layer. Contains the implementation (concrete classes) of an interface defined in the shared layer.
- The purpose of this layer is to hide external dependencies that you do not have control over.
- This layer is useful because it avoids exposing third party dependencies to other components, so if the dependency is changed/removed it should not affect any other component.
- Not all third party dependencies are added in this layer. For example, Entity Framework Core is used directly in the features to avoid introducing more complexity.
- ChatBot. It is an plugin that allows a basic user to schedule appointments from a chatbot.
- Appointment Reminders. It is a plugin that allows to send appointment reminders to patients through a background service.
- SendGrid Email. It is a plugin that allows to send emails in cases such as:
- When a customer registers in the application, an email is sent to confirm the user's email address.
- When a user wants to reset their password, an email is sent with the security token.
- Twilio WhatsApp. It is a plugin that allows to send messages by whatsapp in cases such as:
- When an appointment is scheduled from the chatbot, the user is sent the appointment information to whatsapp.
- When an employee needs to cancel an appointment, he/she should notify patients by whatsapp.
- IdentityDocument.Ecuador. It is a plugin that allows to validate identity documents registered in Ecuador. This plugin uses an algorithm to verify if the identity document is valid or not.
More details
The above diagram describes in more detail which feature modules are contained in the core layer.
In the presented diagram it can be identified that the feature modules are not coupled to each other, the purpose of this is not to cause a dependency hell, in order to maintain a dependency graph that is as simple as possible. The purpose is to make it easier to understand the parts of the backend application.
Direct Line API allows your client application to communicate with the bot. It acts as a bridge between the client and the bot.
For development and test environments you can use InDirectLine to avoid having to use Azure. InDirectLine is a bridge that implements the Direct Line API, but should not be used for production.
By default, the configuration file (.env) contains a key called DIRECT_LINE_BASE_URL
.
DIRECT_LINE_BASE_URL=http://indirectline:3000/
The provider called InDirectLine is used by default.
In production, the value of this key must be changed to:
DIRECT_LINE_BASE_URL=https://directline.botframework.com/
In that case the provider to use will be the Direct Line channel of Azure Bot. The backend application is able to switch providers just by reading the URL.
You can use EF Core migrations to create the database from the entities.
- You must install dotnet ef as a global tool using the following command:
dotnet tool install --global dotnet-ef
- Change directory.
cd src/HostApplication
- Run this command to create the migration files.
dotnet ef migrations add InitialCreate
- At this point you can have EF create your database and create your schema from the migration.
dotnet ef database update
That's all there is to it - your application is ready to run on your new database, and you didn't need to write a single line of SQL. Note that this way of applying migrations is ideal for local development, but is less suitable for production environments - see the Applying Migrations page for more info.
To run the unit tests on your local machine, run this command:
dotnet test ./tests/UnitTests/DentallApp.UnitTests.csproj -c Release
You can also run the chatbot tests on their local machine:
dotnet test ./tests/ChatBot/Plugin.ChatBot.IntegrationTests.csproj -c Release
You can run the integration tests that depend on a database but first you need to follow the following steps:
- Install MariaDb Server and set up your username and password.
- Create a file called
.env
in the root directory with the command:
cp .env.example .env
# On Windows use the "xcopy" command.
- Create a file called
.env.test
in the test directory with the command:
cp ./tests/IntegrationTests/.env.test.example ./tests/IntegrationTests/.env.test
# On Windows use the "xcopy" command.
- Specify your database credentials in the
.env.test
file. - Execute the dotnet test command to run the tests.
dotnet test ./tests/IntegrationTests/DentallApp.IntegrationTests.csproj -c Release
The database credentials you have in the ".env" file may not necessarily be the same as those in the ".env.test" file. For example, the ".env" file may have credentials from a remote AWS database and run the application on your local machine with that connection string.
Any contribution is welcome! Remember that you can contribute not only in the code, but also in the documentation or even improve the tests.
Follow the steps below:
- Fork it
- Create your custom branch (git checkout -b my-new-change)
- Commit your changes (git commit -am 'Add some change')
- Push to the branch (git push origin my-new-change)
- Create new Pull Request