Ozds.Business

This project contains all of the backend logic of the server. Critical functions in this project is tested in the Ozds.Business.Test project. Logic is divided into namespaces conceptually similar to each other (ie. all logic regarding conversion is in the Conversion namespace).

For each namespace an Abstractions namespace is present which contains interfaces that should be used in consuming code. If there is a Agnostic namespace it should be used instead because it automatically handles logic for picking the right implementation of the interface. For example, the AgnosticModelEntityConverter will pick the right implementation of IModelEntityConverter based on the type of the entity or model.

Ozds.Business.Aggregation

This namespace contains client-side and server-side aggregation upsert logic. The server-side logic is used to tell the database how to upsert aggregates via LINQ Expressions over database entities and the client-side logic mimics this as a method over business models . If we were just to use the server-side logic, the database would complain that it couldn't upsert the same aggregate twice in the same transaction. This is why we have to upsert first on client-side the aggregates that are to be inserted and then upsert the aggregates that are on the server.

Ozds.Business.Capabilities

This namespace is a WIP but will be used to detect which meter measures which measures.

Ozds.Business.Conversion

This namespace contains converters for:

  • Database entities ↔ business models

  • Push requests ↔ measurement models

  • Measurement models → aggregate models

Ozds.Business.Finance

This namespace contains billing logic. There are three levels of billing calculations needed for every invoice:

  • Invoice level: this is the top level corresponding to a network user or location and uses lower levels to calculate the totals and subtotals on an invoice

  • Calculation level: each invoice has a set of calculation corresponding to a measurement location and uses the lowest level to calculate the totals and subtotals of a particular calculation

  • Calculation item level: each calculation has a set of calculation items corresponding to a certain billing item and calculates the amounts and totals of a particular billing item

For now, only network user invoice calculation is implemented.

Ozds.Business.Interceptors

This namespace contains interceptors to any request sent to the database and implement various business logic:

  • AggregateCreationInterceptor: intercepts any request to the database containing measurements and creates aggregates for them.
  • AuditingInterceptor: intercepts any request to the database that mutates entities that are auditable, mutates their audit fields and creates an audit event depending on the type of mutation done on the entity.
  • CascadingSoftDeleteInterceptor: this interceptor is a WIP but it is meant to implement soft delete logic for auditable entities.
  • InvoiceIssuingInterceptor: intercepts any invoice creation request and mutates the issuing fields on that invoice.
  • ReadonlyInterceptor: throws an exception any time an attempt to mutate a readonly entity is made.
  • ServedSaveChangesInterceptors: this is a base type for interceptors that is hooked up to provide inheritors with a IServiceProvider.

Ozds.Business.Iot

Contains logic for handling IoT requests. It only handles pushing for now (OzdsIotHandler), but it will be split into a Push and Poll interface once we get around to implement polling.

Pushing is the process of IoT devices sending measurements to the server. It is implemented via a REST API that the IoT devices can call. The IoT devices send measurements which then get aggregated and stored in the database.

Polling is the process of IoT devices asking the server for newly updated configuration. This way we bypass the need to send anything to IoT devices which is problematic in todays internet because of technologies like CGNAT. It is meant to be implemented as a REST API that the IoT devices can call. The IoT devices asks for updated configuration and the server responds with the updated configuration from the database.

Ozds.Business.Math

Contains logic for manipulating electrical measures. This is a critical part of the application that much of the application depends on and is tested thoroughly.

There are three different dimensions each measure can have:

  • Phase: a measure can be a single phase or triphasic measure.

  • Direction: a measure can be an import or export measure and these correspond to user consumption and production. It can also be any duplex measure if it is a measure of current or voltage since these are not directional.

  • Tariff: a measure can be a high tariff and a low tariff or a single tariff measure. This is used to calculate the cost of the measure depending on the time of day.

The measure structure is such that the tariff hierarchy classes contain directional hierarchy classes which contain phase hierarchy classes. All three of these class hierarchies also contain a null class which is used to represent a measure that is not set. These hierarchies also contain a composite class which is used to represent a measure that is a combination of two or more measures. This is used to represent the different ways in which a measure is stored or calculated for better accuracy. For example, we might store a measure as three phases but also as a single phase and for some calculations one is more accurate than the other.

There are two more top-level class hierarchies:

  • Span: a measure can be a measure over a certain time span. This is used to calculate the costs over a span of time.

  • Expenditure: a measure can be an amount used to calculate costs. For network user invoice calculations this means it can be either used to calculate supply or usage costs and is used to represent these two different types of costs.

Ozds.Business.Models

Contains business models that are used to represent entities in the database. Entities in the database are represented as entity classes in the Ozds.Data.Entities namespace which are then converted to business models in this namespace. The reasoning is that the database entity classes have special fields or properties that instruct Entity Framework Core how to handle database operations that should not be exposed to the rest of the application. On the other hand, business models have special fields and properties and implement interfaces which should not be represented in the database.

There are a couple of marker interfaces that are used to represent the different aspects of models:

  • Identifiable: models with an identifier. Additionally, all models that are identifiable should have a title which is a nice addition to display them in the UI.

  • Readonly: models that are readonly and should not be mutated.

Models are divided into a few of class hierarchies:

  • Auditable: models that can be audited. Any time a mutation is done on an auditable entity, an audit event is created and the audit fields on the entity are updated.

  • Events: models that represent events like when an auditable entity is mutated.

  • Measurements: models that represent measurements that are sent by IoT devices. In order to use models from multiple device types all measurement models are required to implement the different measure properties on IMeasurement.

  • Aggregates: models that represent aggregates of measurements. In order to use models from multiple device types all aggregate models are required to implement the different measure properties on IAggregate. The Timestamp is always the start of the span of the aggregate. The Count is the number of measurements that were aggregated. The non SpanningMeasure energy properties are the values of the last measurement in the span while the non SpanningMeasure non energy properties are the average values of the measurements in the span.

  • Invoices: models that represent invoices that are sent to network users.

  • Calculations: models that represent calculations that are used to calculate the totals and subtotals on an invoice.

In addition, all class hierarchies implement IValidatableObject.

Ozds.Business.Mutations

Contains classes that wrap database requests in functions that mutate data. These are separate from other requests in Ozds.Business.Queries because mutations are always more sensitive and should be handled with care. All the classes use AgnosticModelEntityConverter from Ozds.Business.Conversion to convert the mutated business models to database entities and DataDbContext from Ozds.Data to mutate data in the database.

Most mutations are done via agnostic classes that operate on class hierarchies in Ozds.Business.Models:

  • Auditable: mutations that mutate auditable entities.

  • Events: mutations that mutate events. This class hierarchy is readonly so only the create mutation is implemented.

  • Measurements: mutations that mutate measurements. This class hierarchy is readonly so only the create mutation is implemented.

  • Aggregates: mutations that mutate aggregates. This class hierarchy is readonly so only the create mutation is implemented.

  • Invoices: mutations that mutate invoices. This class hierarchy is readonly so only the create mutation is implemented.

  • Calculations: mutations that mutate calculations. This class hierarchy is readonly so only the create mutation is implemented.

Ozds.Business.Queries

Contains classes that wrap database requests in functions that query data. These are separate from other requests in Ozds.Business.Mutations because queries are always less sensitive and should be handled with less care. All the classes use DataDbContext from Ozds.Data to query data in the database and AgnosticModelEntityConverter from Ozds.Business.Conversion to convert the queried database entities to business models.

Query classes use the PaginatedList<T> class that makes it easier to paginate the results of a query.

Query classes also use the Z.EntityFramework.Plus.EFCore nuget package to make where and order by clauses agnostic over the type of entity. This makes the code more brittle but for now it is a quick and dirty solution to get started before we convert everything to use standard LINQ expressions.

Most queries are done via specifically implemented classes but a lot can be queried via agnostic classes that operate on class hierarchies in Ozds.Business.Models:

  • Auditable: queries that query auditable entities.

  • Events: queries that query events.

  • Measurements: queries that query measurements.

  • Aggregates: queries that query aggregates.

  • Invoices: queries that query invoices.

  • Calculations: queries that query calculations.

Ozds.Business.Time

Contains all logic for handling time. This is a critical part of the application that much of the application depends on and is tested thoroughly.