Skip to content

Commit

Permalink
Added additional comments for composition root.
Browse files Browse the repository at this point in the history
  • Loading branch information
spencerohegartyDfE committed Jul 29, 2024
1 parent 32fc2c1 commit 583ddb2
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 48 deletions.
19 changes: 14 additions & 5 deletions Dfe.Data.Common.Infrastructure.CognitiveSearch/CompositionRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@
namespace Dfe.Data.Common.Infrastructure.CognitiveSearch;

/// <summary>
///
/// The composition root provides a unified location in the application where the composition
/// of the object graphs for the application take place, using the dependency injection (IOC).
/// </summary>
public static class CompositionRoot
{
/// <summary>
///
/// Extension method which provides all the pre-registrations required to
/// access azure search services, and perform searches across provisioned indexes.
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <param name="services">
/// The originating application services onto which to register the search dependencies.
/// </param>
/// <param name="configuration">
/// The originating configuration block from which to derive search service settings.
/// </param>
/// <exception cref="ArgumentNullException">
/// The exception thrown if no valid T:Microsoft.Extensions.DependencyInjection.IServiceCollection
/// is provisioned.
/// </exception>
public static void AddDefaultCognitiveSearchServices(this IServiceCollection services, IConfiguration configuration)
{
if (services is null)
Expand Down
112 changes: 69 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,90 @@ In order to leverage the search functionality it is necessary to register a numb
In order to use the default search services it is possible to register all dependcies listed under the default composition root, in one registration, as follows:

```
builder.Services.RegisterDefaultSearchServices(builder.Configuration);
builder.Services.AddDefaultCognitiveSearchServices(builder.Configuration);
```

Alternatively, the registrations can be configured in the consuming application IOC container, with a typical registration configured as follows:

```
builder.Services.AddAzureCognitiveSearchProvider(builder.Configuration);
builder.Services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter<Infrastructure.Establishment>));
builder.Services.AddScoped<IUseCase<SearchByKeywordRequest, SearchByKeywordResponse>, SearchByKeywordUseCase>();
builder.Services.AddSingleton(typeof(IMapper<Response<SearchResults<Infrastructure.Establishment>>, EstablishmentResults>), typeof(AzureSearchResponseToEstablishmentResultMapper));
builder.Services.AddSingleton<IMapper<SearchSettingsOptions, SearchOptions>, SearchOptionsToAzureOptionsMapper>();
builder.Services.AddSingleton<IMapper<SearchByKeywordResponse, SearchResultsViewModel>, SearchByKeywordResponseToViewModelMapper>();
builder.Services.AddSingleton<IMapper<Infrastructure.Establishment, Search.Establishment>, AzureSearchResultToEstablishmentMapper>();
builder.Services.AddSingleton<IMapper<EstablishmentResults, SearchByKeywordResponse>, ResultsToResponseMapper>();
builder.Services.AddOptions<SearchSettingsOptions>("establishments")
.Configure<IConfiguration>(
(settings, configuration) =>
configuration.GetSection("AzureCognitiveSearchOptions:SearchEstablishment:SearchSettingsOptions").Bind(settings));
builder.Services.AddScoped<ISearchOptionsFactory, SearchOptionsFactory>();
```
services.TryAddSingleton<ISearchByKeywordClientProvider, SearchByKeywordClientProvider>();
services.TryAddSingleton<ISearchIndexNamesProvider, SearchIndexNamesProvider>();
services.TryAddSingleton<ISearchByKeywordService, DefaultSearchByKeywordService>();
services.TryAddScoped<IGeoLocationClientProvider, GeoLocationClientProvider>();
services.TryAddScoped<IGeoLocationService, DefaultGeoLocationService>();
services.AddOptions<SearchByKeywordClientOptions>()
.Configure<IConfiguration>(
(settings, configuration) =>
configuration
.GetSection(nameof(SearchByKeywordClientOptions))
.Bind(settings));
services.AddOptions<GeoLocationOptions>()
.Configure<IConfiguration>(
(settings, configuration) =>
configuration
.GetSection(nameof(GeoLocationOptions))
.Bind(settings));
services.AddHttpClient("GeoLocationHttpClient", config =>
{
var geoLocationOptions =
configuration
.GetSection(nameof(GeoLocationOptions)).Get<GeoLocationOptions>();
ArgumentNullException.ThrowIfNull(geoLocationOptions);
ArgumentNullException.ThrowIfNullOrWhiteSpace(geoLocationOptions.MapsServiceUri);
config.BaseAddress = new Uri(geoLocationOptions.MapsServiceUri);
config.Timeout =
new TimeSpan(
geoLocationOptions.RequestTimeOutHours,
geoLocationOptions.RequestTimeOutMinutes,
geoLocationOptions.RequestTimeOutSeconds);
config.DefaultRequestHeaders.Clear();
});```
### Code Usage/Examples
Typical dependency injection and search request would look something like the following,
```
public class HomeController : Controller
public sealed class CognitiveSearchServiceAdapter<TSearchResult> : ISearchServiceAdapter where TSearchResult : class
{
private readonly IUseCase<SearchByKeywordRequest, SearchByKeywordResponse> _searchByKeywordUseCase;
private readonly IMapper<SearchByKeywordResponse, SearchResultsViewModel> _mapper;
public HomeController(
ILogger<HomeController> logger,
IUseCase<SearchByKeywordRequest, SearchByKeywordResponse> searchByKeywordUseCase,
IMapper<SearchByKeywordResponse, SearchResultsViewModel> mapper)
private readonly ISearchService _cognitiveSearchService;
private readonly ISearchOptionsFactory _searchOptionsFactory;
private readonly IMapper<Response<SearchResults<TSearchResult>>, EstablishmentResults> _searchResponseMapper;

public CognitiveSearchServiceAdapter(
ISearchService cognitiveSearchService,
ISearchOptionsFactory searchOptionsFactory,
IMapper<Response<SearchResults<TSearchResult>>, EstablishmentResults> searchResponseMapper)
{
_searchByKeywordUseCase = searchByKeywordUseCase;
_mapper = mapper;
_searchOptionsFactory = searchOptionsFactory;
_cognitiveSearchService = cognitiveSearchService;
_searchResponseMapper = searchResponseMapper;
}

public async Task<IActionResult> Index(string searchKeyWord)
public async Task<EstablishmentResults> SearchAsync(SearchContext searchContext)
{
if (string.IsNullOrEmpty(searchKeyWord))
{
return View();
}
ViewBag.SearchQuery = searchKeyWord;
SearchByKeywordResponse response =
await _searchByKeywordUseCase.HandleRequest(
new SearchByKeywordRequest(searchKeyWord, "establishments"));
SearchResultsViewModel viewModel = _mapper.MapFrom(response);
return View(viewModel);
}
}
```
SearchOptions searchOptions =
_searchOptionsFactory.GetSearchOptions(searchContext.TargetCollection) ??
throw new ApplicationException(
$"Search options cannot be derived for {searchContext.TargetCollection}.");

Response<SearchResults<TSearchResult>> searchResults =
await _cognitiveSearchService.SearchAsync<TSearchResult>(
searchContext.SearchKeyword,
searchContext.TargetCollection,
searchOptions
)
.ConfigureAwait(false) ??
throw new ApplicationException(
$"Unable to derive search results based on input {searchContext.SearchKeyword}.");

return _searchResponseMapper.MapFrom(searchResults);
}```

## Built With

Expand Down

0 comments on commit 583ddb2

Please sign in to comment.