|
| 1 | +# C\# Client Library |
| 2 | + |
| 3 | +This document outlines the designs behind the GSF Carbon Aware C# Client |
| 4 | +Library. |
| 5 | + |
| 6 | +## Namespace |
| 7 | + |
| 8 | +Given the fact this is going to be a library exposing functionality to |
| 9 | +consumers, will use the |
| 10 | +[standard](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-namespaces) |
| 11 | +namespace naming schema: |
| 12 | +`<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]`. For GSF |
| 13 | +CarbonAware SDK this the following schema: |
| 14 | + |
| 15 | +- **Company**: **_GSF_** |
| 16 | +- **Product**: **_CarbonAware_** |
| 17 | +- **Feature**: **_Models_**, **_Handlers_**, ... |
| 18 | + |
| 19 | +An example of a namespace would be: `namespace GSF.CarbonAware.Models` and a |
| 20 | +class (record, interface, ...) that belongs to that namespace would be: |
| 21 | + |
| 22 | +```c# |
| 23 | +namespace GSF.CarbonAware.Models; |
| 24 | + |
| 25 | +public record EmissionsData |
| 26 | +{ |
| 27 | + .... |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +The following namespaces are included: |
| 32 | + |
| 33 | +| namespace | |
| 34 | +| ----------------------------- | |
| 35 | +| GSF.CarbonAware.Exceptions | |
| 36 | +| GSF.CarbonAware.Configuration | |
| 37 | +| GSF.CarbonAware.Handlers | |
| 38 | +| GSF.CarbonAware.Models | |
| 39 | +| GSF.CarbonAware.Parameters | |
| 40 | + |
| 41 | +## Features |
| 42 | + |
| 43 | +### Models |
| 44 | + |
| 45 | +There are two main classes that represents the data fetched from the data |
| 46 | +sources (i.e `Static Json`, [WattTime](https://www.watttime.org), |
| 47 | +[ElectricityMaps](https://www.electricitymaps.com), and |
| 48 | +[ElectricityMapsFree](https://www.co2signal.com/)): |
| 49 | + |
| 50 | +- `EmissionsData` |
| 51 | +- `EmissionsForecast` |
| 52 | + |
| 53 | +A record is defined for each of these data types owned by the library. |
| 54 | + |
| 55 | +```c# |
| 56 | +namespace GSF.CarbonAware.Models; |
| 57 | +public record EmissionsData |
| 58 | +{ |
| 59 | + string Location |
| 60 | + DateTimeOffset Time |
| 61 | + double Rating |
| 62 | + TimeSpan Duration |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +```c# |
| 67 | +namespace GSF.CarbonAware.Models; |
| 68 | +public record EmissionsForecast |
| 69 | +{ |
| 70 | + DateTimeOffset RequestedAt |
| 71 | + DateTimeOffset GeneratedAt |
| 72 | + IEnumerable<EmissionsData> EmissionsDataPoints |
| 73 | + IEnumerable<EmissionsData> OptimalDataPoints |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +The user can expect to either have a primitive type (such as an int) or one of |
| 78 | +these specific models as a return type of the **Handlers**. |
| 79 | + |
| 80 | +### Handlers |
| 81 | + |
| 82 | +There will be two handlers for each of the data types returned: |
| 83 | + |
| 84 | +- `EmissionsHandler` |
| 85 | +- `ForecastHandler` |
| 86 | + |
| 87 | +Each is responsible for interacting on its own domain. For instance, |
| 88 | +EmissionsHandler can have a method `GetAverageCarbonIntensityAsync()` to pull |
| 89 | +EmissionsData data from a configured data source and calculate the average |
| 90 | +carbon intensity. ForecastHandler can have a method `GetCurrentAsync()`, that |
| 91 | +will return a EmissionsForecast instance. (**Note**: The current core |
| 92 | +implementation is using async/await paradigm, which would be the default for |
| 93 | +library too). |
| 94 | + |
| 95 | +In addition, there is a `LocationHandler` that is responsible for retrieving all |
| 96 | +the locations supported by the underlying datasource. |
| 97 | + |
| 98 | +### Parameters |
| 99 | + |
| 100 | +Both handlers require that exact fields be passed in as input. Within the docs |
| 101 | +of each library function, we specifically call out which fields the function |
| 102 | +expects to be defined versus which are optional. Internally, we handle creating |
| 103 | +the CarbonAwareParameters object and validating the fields through that. |
| 104 | + |
| 105 | +## Carbon Aware Parameters |
| 106 | + |
| 107 | +The `CarbonAwareParameters` class allows the user to pass in a unique parameter |
| 108 | +instance to the public methods in the Handlers with the specific parameters |
| 109 | +needed by that call. The list of allowed parameters is defined in the class and |
| 110 | +includes |
| 111 | + |
| 112 | +- SingleLocation |
| 113 | +- MultipleLocations |
| 114 | +- Start |
| 115 | +- End |
| 116 | +- RequestedAt |
| 117 | +- Duration |
| 118 | + |
| 119 | +### Parameter Display Names |
| 120 | + |
| 121 | +The display name of each parameter can be overriden using the public setter. By |
| 122 | +default, each parameter display name is set to the variable name (ex: |
| 123 | +`Start = "start"`). The parameter display names are used when creating the |
| 124 | +validation error messages. Overriding them is useful in situations where the |
| 125 | +variables the user is using for input don't exactly match the default display |
| 126 | +name of the parameter (e.g. the user variable in the controller is |
| 127 | +`periodStartTime` instead of `startTime`). That way, when the error is thrown to |
| 128 | +the user, the parameter names will match the users' expectation |
| 129 | + |
| 130 | +To do the override, define a class that inherits from |
| 131 | +CarbonAwareParametersBaseDTO and uses the [FromQuery(Name = |
| 132 | +"myAwesomeDisplayName")] or [JsonPropertyName("myAwesomeDisplayName")] |
| 133 | +attribute. A second (less recommended) option is to pass the optional arg |
| 134 | +Dictionary<string, string>? displayNameMap when you are directly creating the |
| 135 | +object. With either option, the SDK handles updating references internally. |
| 136 | + |
| 137 | +### Required Properties |
| 138 | + |
| 139 | +The first core check the parameters class does is validating that required |
| 140 | +parameters are defined. By default, all parameters are considered optional. |
| 141 | +Calling the `SetRequiredProperties(...)` function with the desired arguments |
| 142 | +sets the required parameters for the instance. |
| 143 | + |
| 144 | +```csharp |
| 145 | + /// <summary> |
| 146 | + /// Accepts any PropertyNames as arguments and sets the associated property as required for validation. |
| 147 | + /// </summary> |
| 148 | + public void SetRequiredProperties(params PropertyName[] requiredProperties) |
| 149 | +``` |
| 150 | + |
| 151 | +### Validations |
| 152 | + |
| 153 | +The second core check the parameters class does is enforcing validations on the |
| 154 | +parameters themselves. Some common examples include |
| 155 | + |
| 156 | +- Relationship validations: _`start < end` must be true_ |
| 157 | +- Content validations: _`list.Any()` must be true for list fields_ |
| 158 | + |
| 159 | +Calling the `SetValidations(...)` function with the desired arguments sets the |
| 160 | +validations for the instance. |
| 161 | + |
| 162 | +```csharp |
| 163 | + /// <summary> |
| 164 | + /// Accepts any ValidationName as arguments and sets the associated validation to check. |
| 165 | + /// </summary> |
| 166 | + public void SetValidations(params ValidationName[] validationNames) |
| 167 | +``` |
| 168 | + |
| 169 | +### Validate |
| 170 | + |
| 171 | +Calling the `Validate(...)` function validates (1) required parameters and (2) |
| 172 | +specified validations. Currently, the only validation we check is whether |
| 173 | +`start` is before `end`. |
| 174 | + |
| 175 | +If no errors are thrown, the function simply returns. If any validation errors |
| 176 | +are found, they are packaged into a single `ArgumentException` error with each |
| 177 | +being part of the `data` dictionary. |
| 178 | + |
| 179 | +``` |
| 180 | + /// <summary> |
| 181 | + /// Validates the properties and relationships between properties. Any validation errors found are packaged into an |
| 182 | + /// ArgumentException and thrown. If there are no errors, simply returns void. |
| 183 | + /// </summary> |
| 184 | + public void Validate() |
| 185 | +``` |
| 186 | + |
| 187 | +### Getters With Default Fallbacks |
| 188 | + |
| 189 | +Certain parameters have special getters that allow you to define a fallback |
| 190 | +default value if the parameter is null. This can be useful in cases where a |
| 191 | +parameter is optional, so you want to get it if it was defined by the user, or |
| 192 | +otherwise fallback to a specific default. These include `Start`, `End`, |
| 193 | +`Requested`,and `Duration` |
| 194 | + |
| 195 | +``` |
| 196 | + DateTimeOffset StartOrDefault(DateTimeOffset defaultStart) |
| 197 | + DateTimeOffset EndOrDefault(DateTimeOffset defaultEnd) |
| 198 | + DateTimeOffset RequestedOrDefault(DateTimeOffset defaultRequested) |
| 199 | + TimeSpan DurationOrDefault |
| 200 | + |
| 201 | +``` |
| 202 | + |
| 203 | +### Error Handling |
| 204 | + |
| 205 | +The `CarbonAwareException` class is used to report errors to the consumer. It |
| 206 | +follows the `Exception` class approach, where messages and details are provided |
| 207 | +as part of error reporting. |
| 208 | + |
| 209 | +## References |
| 210 | + |
| 211 | +<https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/> |
0 commit comments