The Vertical Slice

Oct 5, 2021 – One down, three to go

See the code for this post on GitHub

I've now got a full vertical slice for retrieving a user's observations from CosmosDB and pulling them into the Svelte observation store. I was able to make a few improvements to the app's architecture along the way.

I simply could not let the AutoMapper configuration continue taking up space directly in the Azure Function code. I moved mapper configuration to its own extension method and configured DI to inject an IMapper instance for use where it's needed.

public static void AddMapperConfigurations(this IServiceCollection services)
{
    services.AddSingleton<IMapper>(serviceProvider => {
        var cfg = new MapperConfiguration(cfg => {
            cfg.CreateMap<Observation, ObservationModel>()
                .ReverseMap();
            cfg.AddCelestialObjectMapperConfiguration();
        });

        return cfg.CreateMapper();
    });
}

More complicated mapping configurations, like the one for Celestial Objects, can live in their own files. I've seen mapping configuration files grow to unreasonable sizes, so I like to break them up when it makes sense.

One challenge I had to solve for was retrieving the user's ID from within the Azure function. I repurposed some example code from Microsoft's documentation for retrieving the user's claims principal. Azure Static Web Apps adds this data under the x-ms-client-principal header from the edge node when a logged-in user makes a request to a Function endpoint. The UserExtensions class has methods for retrieving this data and parsing the User's ID from it.

Another improvement I made in this merge was to move repository calls (remember them from the last post?) to their own services. This allows me to move a bit more logic out of the Function endpoint code itself. For example, constructing the Celestial Object query definition, calling the repository, and mapping the results have all been moved to a service method.

public async Task<List<CelestialObjectModel>> GetCelestialObjectsBySearchTermAsync(string searchTerm)
{
    var queryDef = new QueryDefinition(Constants.QueryStrings.GetCelestialObjectsBySearchTerm)
        .WithParameter("@term", searchTerm);

    var items = await _celestialObjectRepository.GetItemsAsync<CelestialObject>(queryDef);

    List<CelestialObjectModel> results
        = items.Select(item => _mapper.Map<CelestialObjectModel>(item)).ToList();

    return results;
}

Turning client side, this latest merge adds some code to the observation store to fetch the user's observations on page load and set a loading indicator when a server call is in progress.

export const isLoading = readable(true, (set: Subscriber<boolean>) => {
    _setIsLoading = set;
});

export class ObservationStore implements IObservationStore {
    constructor() {
        _setIsLoading(true);

        fetch(`/api/Observations`)
            .then(result => result.json())
            .then((observations: ObservationModel[]) => {
                this._store.set(observations.map(this.reviveDate));
            })
            .finally(() => _setIsLoading(false));
    }

    /* class definition continues... */
}

That's one vertical slice for complete Observation CRUD operations. Next up, I'll tackle inserting new observations.

© 2022 Andrew Iafrate. Contact me for questions or comments.