The OwnLight.DeviceService is a crucial component of the OwnLightSystem, a microservice-based architecture developed for the second semester project. This service handles all device-related functionalities, including registration, control, and monitoring of devices such as luminaires. Additionally, it manages the associations between devices and users, enabling seamless control and monitoring within the system.
The OwnLight.DeviceService is designed with a focus on scalability, maintainability, and flexibility, adhering to Domain-Driven Design (DDD) principles. It employs the CQRS (Command Query Responsibility Segregation) pattern to distinctly separate read and write operations, enhancing performance and simplifying the codebase. The microservice architecture ensures modularity, allowing the service to interact seamlessly with other services through well-defined APIs, facilitating future expansion and integration.
- Controllers: Handle incoming HTTP requests and route them to the appropriate handlers. They are organized by device functionalities (e.g., registration, control, monitoring).
- Services: Centralize business logic, such as device management, message handling, and validation. These services act as intermediaries between controllers and repositories.
- Repositories: Manage the database interaction using Entity Framework to perform CRUD operations on device-related data.
- Entities: Represent the core domain models like Device and other related domain objects.
- Mappings: Use AutoMapper to map between domain entities and DTOs (Data Transfer Objects), simplifying data handling between layers.
- MediatR Handlers: Handle the commands and queries defined in the CQRS architecture, decoupling the execution logic from controllers.
The project is organized into multiple layers based on the responsibilities, ensuring a clean separation of concerns:
OwnLight.DeviceService/
├── DeviceService.API/
│ ├── Controllers/ # Handles HTTP requests (Device controllers)
│ ├── Program.cs # Application startup configuration
│ ├── APIServiceRegistration.cs # Registers services and dependencies
│ └── Properties/
│ └── appsettings.json # Application settings and configuration
│
├── DeviceService.Application/
│ ├── Common/
│ │ ├── Mappings/ # AutoMapper profiles for entity to DTO mappings
│ │ ├── Services/ # Business logic (Device management, Message services)
│ │ └── Validation/ # Validation logic for various operations
│ ├── DTO's/ # Data Transfer Objects for API responses and requests
│ ├── Features/
│ │ ├── Device/ # Handlers, Commands, Queries related to Device
│ └── ApplicationServiceRegistration.cs # Registers application services
│
├── DeviceService.Domain/
│ ├── Entities/ # Domain entities (e.g., Device, DeviceType)
│ ├── Interfaces/ # Domain interfaces (e.g., IDeviceRepository)
│ └── Primitives/ # Basic domain concepts and value objects
│
├── DeviceService.Infrastructure/
│ ├── Data/ # Database context and configurations
│ ├── Repositories/ # Concrete implementations of the domain repositories
│ └── InfrastructureServiceRegistration.cs # Registers infrastructure services
│
└── Migrations/ # Database migrations for setting up and updating the schema
The OwnLight.DeviceService uses a PostgreSQL database to manage device-related data. Below are the schemas for the Device, DeviceAction, and DeviceType tables:
Column Name | Data Type | Constraints |
---|---|---|
Id | uuid | Primary Key |
Name | varchar(30) | Not Null |
TypeId | uuid | Foreign Key |
Status | varchar(30) | Not Null |
CreatedAt | timestamp | Default: UtcNow |
UpdatedAt | timestamp | |
UserId | uuid | Foreign Key |
Column Name | Data Type | Constraints |
---|---|---|
Id | uuid | Primary Key |
DeviceId | uuid | Foreign Key |
Action | varchar(30) | Not Null |
PerformedAt | timestamp | Default: UtcNow |
Column Name | Data Type | Constraints |
---|---|---|
Id | uuid | Primary Key |
TypeName | varchar(30) | Not Null |
Description | text |
-
Device Table:
- Id: A unique identifier for each device.
- Name: The name of the device.
- TypeId: The unique identifier for the type of the device.
- Status: The current status of the device (e.g., active, inactive).
- CreatedAt: Timestamp of when the device was created.
- UpdatedAt: Timestamp of the last update to the device's information.
- UserId: A unique identifier for the user associated with the device.
-
DeviceAction Table:
- Id: A unique identifier for each action.
- DeviceId: A unique identifier for the device associated with the action.
- Action: The action performed on the device (e.g., turn on, turn off).
- PerformedAt: Timestamp of when the action was performed.
-
DeviceType Table:
- Id: A unique identifier for each device type.
- TypeName: The name of the device type.
- Description: A description of the device type. (yet to implement)
To start a Domain-Driven Design (DDD) project with the described pattern, follow these steps:
- Create the Solution and Projects
First, create a new solution and the necessary projects:
dotnet new sln -n OwnLightSystem
dotnet new classlib -n DeviceService.Domain
dotnet new classlib -n DeviceService.Infrastructure
dotnet new classlib -n DeviceService.Application
dotnet new webapi -n DeviceService.API
- Add Projects to the Solution
Add the created projects to the solution:
dotnet sln OwnLightSystem.sln add DeviceService.Domain/DeviceService.Domain.csproj
dotnet sln OwnLightSystem.sln add DeviceService.Infrastructure/DeviceService.Infrastructure.csproj
dotnet sln OwnLightSystem.sln add DeviceService.Application/DeviceService.Application.csproj
dotnet sln OwnLightSystem.sln add DeviceService.API/DeviceService.API.csproj
- Set Up Project References
Set up the necessary project references:
The Domain Layer does not have any project references because it follows the Domain-Driven Design (DDD) pattern, which emphasizes the independence of the domain model from other layers.
dotnet add DeviceService.Infrastructure/DeviceService.Infrastructure.csproj reference DeviceService.Domain/DeviceService.Domain.csproj
dotnet add DeviceService.Application/DeviceService.Application.csproj reference DeviceService.Domain/DeviceService.Domain.csproj
dotnet add DeviceService.API/DeviceService.API.csproj reference DeviceService.Domain/DeviceService.Domain.csproj
dotnet add DeviceService.API/DeviceService.API.csproj reference DeviceService.Application/DeviceService.Application.csproj
dotnet add DeviceService.API/DeviceService.API.csproj reference DeviceService.Infrastructure/DeviceService.Infrastructure.csproj
- Install Required Packages
Install the required packages for each project:
The Domain Layer does not have any required packages. However, if you prefer to include validation logic within the domain, you can add the FluentValidation package:
dotnet add DeviceService.Domain package FluentValidation
# Install necessary packages for Infrastructure project
dotnet add OwnLight.Infrastructure package Npgsql.EntityFrameworkCore.PostgreSQL
# Install necessary packages for Application project
dotnet add DeviceService.Application package AutoMapper
dotnet add DeviceService.Application package MediatR.Extensions.Microsoft.DependencyInjection
dotnet add DeviceService.Application package FluentValidation.AspNetCore
# Install necessary packages for API project
dotnet add OwnLight.Application package MediatR.Extensions.Microsoft.DependencyInjection
Observation: Including Entity Framework Core Design in the API project is my personal choice. If you prefer, you can add it to the Infrastructure project instead. In that case, when applying migrations, ensure you reference the Infrastructure project as the startup project instead of the API project.
- Configure the API Project
Configure the DeviceService.API
project to use the services and dependencies from the other layers. Update the Program.cs
and Startup.cs
(if applicable) to register services from the Application
and Infrastructure
layers. In this project I adopted a pattern of LayerServiceRegistration
, basically every dependency of the layer is registered on its class. And then, the APIServiceRegistration instantiate both Infrastructure and Application registration methods, in order to make the program.cs way cleaner. If you prefer, you could use a Startup.cs
class to modularize dependency injection logic. I recommend following this projects pattern though.
- Run the Application
Finally, run the application to ensure everything is set up correctly:
dotnet run --project DeviceService.API/DeviceService.API.csproj
By following these steps, you will have a basic setup for a DDD project with a clean architecture, ready for further development.
Ensure you have the following libraries installed for the project:
- Entity Framework Core: This library is used for database interactions, allowing you to perform CRUD operations and manage database migrations seamlessly.
- AutoMapper: AutoMapper is utilized for object-object mapping, simplifying the process of converting domain entities to Data Transfer Objects (DTOs) and vice versa.
- MediatR: MediatR is implemented to support the CQRS (Command Query Responsibility Segregation) pattern, helping to decouple the execution logic from the controllers by handling commands and queries.
- FluentValidation: FluentValidation is used to define and enforce validation rules for your models, ensuring data integrity and consistency throughout the application.
- Swashbuckle.AspNetCore: This library integrates Swagger into your ASP.NET Core project, providing interactive API documentation and testing capabilities.
These libraries collectively enhance the functionality, maintainability, and scalability of the project by addressing various concerns such as data access, object mapping, command/query handling, validation, and API documentation.
Ensure you have the following installed:
- .NET 8.0 SDK
- PostgreSQL
Clone the repository:
git clone <repository-url>
Navigate to the project directory:
cd OwnLight.DeviceService
Restore dependencies:
dotnet restore
If you wish to make your own migrations (while on infrastructure directory):
dotnet ef migrations add YourMigration --startup-project ..\DeviceService.API\
Apply database migrations (while on infrastructure directory):
dotnet ef database update --startup-project ..\DeviceService.API\
Run the service:
dotnet run .\DeviceService.API\
The OwnLight.DeviceService is configured using the appsettings.json
file. Below are some of the key settings:
- ConnectionStrings: Defines the connection to the PostgreSQL database.
- DeviceSettings: Contains settings related to device management and monitoring.
The OwnLight.DeviceService exposes several endpoints to handle device functionalities:
GET /api/devices
: Retrieve a list of devices.POST /api/devices
: Create a new device.GET /api/devices/{id}
: Retrieve a device by ID.PUT /api/devices/{id}
: Update a device by ID.DELETE /api/devices/{id}
: Delete a device by ID.
GET /api/devicetypes
: Retrieve a list of device types.POST /api/devicetypes
: Create a new device type.GET /api/devicetypes/{id}
: Retrieve a device type by ID.PUT /api/devicetypes/{id}
: Update a device type by ID.DELETE /api/devicetypes/{id}
: Delete a device type by ID.
GET /api/deviceactions
: Retrieve a list of device actions.POST /api/deviceactions
: Create a new device action.GET /api/deviceactions/{id}
: Retrieve a device action by ID.PUT /api/deviceactions/{id}
: Update a device action by ID.DELETE /api/deviceactions/{id}
: Delete a device action by ID.POST /api/devices/{id}/control
: Control a device (e.g., turn on/off).GET /api/devices/{id}/status
: Retrieve the status of a device.
All responses follow standard REST patterns, returning appropriate HTTP status codes (200, 400, 404, etc.) and messages.
Contributions are welcome! Please follow the standard Git workflow:
- Fork the repository
- Create a new feature branch (
git checkout -b feature/my-feature
) - Commit your changes (
git commit -am 'Add new feature'
) - Push to the branch (
git push origin feature/my-feature
) - Open a pull request
This project is licensed under the MIT License. See the LICENSE file for more details.
This API is part of a microservice architecture-based project for my college (FACENS) UPX subject. Later it will be connected to an Ocelot API Gateway and will be related to other ASP.NET APIs such as:
UserService
: Responsible for managing user-related functionalities.AutomationService
: Responsible for registering rooms, groups, and schedules for the devices.EnergyService
: Responsible for monitoring the energy costs of all the devices.
The mix of all the microservices, Ocelot Gateway, databases, and the front-end will be our app called OwnLight.
Thanks for the attention, and see you on my next projects!