...i jak nam w tym pomógł message-passing oraz feature-driven development
/frydrychewicz
/thecorrado
import {route, inject, translated, customElement} from 'app/app'; import {actions} from '../auth.store'; import {Dispatcher} from 'app/core/flux'; import {User} from '../user'; import {DeskId} from 'app/structure'; import {Alert} from 'app/common/ui'; ...
@customElement('search-agents') @inject(CitiesResource, Dispatcher, State, StatesResource, AgencyInformationResource, Moment) export class SearchAgentsComponent { public city: string; public selectedCity: string; public selectedState: State; public states: State[]; public agents: ItWorks.Api.Agency.AgentInformation[]; @bindable.expression public country: string; @bindable.value public size: number; constructor( private citiesResource: CitiesResource, private dispatcher: Dispatcher, private state: State, private statesResource: StatesResource, private agencyInformationResource: AgencyInformationResource, private moment: Moment) {} ... }
import {customElement, inject, bindable, route} from 'app/app'; import {PreferredCountriesResource} from './preferredCountries.resource' import {AgencyId} from 'app/structure/agency'; import "./preferredCountries.css!"; @customElement('preferred-countries') @inject(PreferredCountriesResource) @route('iw.auth.preferredCountries', { url: '/components/preferred-countries' }) @translate export class PreferredCountries { ... }
import {customAttribute, withoutTemplate, element, bindable, inject} from 'app/app' import {Parse} from 'app/core/angular'; import {AuthProvider} from './provider'; import * as _ from 'lodash'; @customAttribute('iw-authorize') @withoutTemplate @inject(Parse) export class AuthorizeAttribute { ... }
import {customElement, inject, bindable, route} from 'app/app'; import {PreferredCountriesResource} from './preferredCountries.resource' import {AgencyId} from 'app/structure/agency'; import "./preferredCountries.css!"; @customElement('preferred-countries') @inject(PreferredCountriesResource) @route('iw.auth.preferredCountries', { url: '/components/preferred-countries' }) @translate export class PreferredCountries { ... }
@route('iw.auth.login', { url: '/login' }) @translated @inject(Dispatcher, User, Alert) export class LogIn { ... }
[ ... { "name": "iw.auth.login", "controller": "LogIn", "module": "app/auth/login/login", "options": { "url": "/login" } }, ... ]
import {DynamicFormsResource} from 'app/common/dynamicForms/dynamicForms.resource'; @customElement('customer-form') @inject(DynamicFormsResource) export class CustomerForm { constructor(private dynamicFormsResource: DynamicFormsResource) {} activate() { return this.formSchemaPromise = this.dynamicFormsResource .getForm(ItWorks.Api.DynamicForms.FormType.Customer) .then(schema => { ... }); } ... }
<iw-dynamic-form schema="vm.formSchemaPromise" on-submit="vm.submit()"> ... </iw-dynamic-form>
@route('iw.auth.login', { url: '/login', version: 1 }) @inject(SomeResource) export class Login { ... }
@route('iw.auth.login', { url: '/login', version: 2 }) @inject(NewVersionOfSomeResource, AnotherService) export class Login { ... }
...i wspieramy IE 8 :-)
Message Passing
public interface IDispatcher { OutEnvelope<TResponse> Dispatch<TRequest, TResponse>(TRequest request) where TRequest : Request<TResponse> where TResponse : class; }
public interface IHandleRequest<TMessage, TResponse> : IHandleRequest where TMessage : Request<TResponse> where TResponse : class { OutEnvelope<TResponse> Handle(TMessage request); }
IHandleRequest przykład
public class GetSapleDataHandler : IHandleRequest<GetSampleData.Request, IEnumerable<string>> { private readonly SqlConnection _connection; ...ctor... public OutEnvelope<IEnumerable<string>> Handle(GetSampleData.Request request) { var rows = _connection.Query<SomeRow>(@" SELECT [Something] FROM [Somewhere]"); if (rows == null) return OutEnvelope<IEnumerable<string>>.NotFound(); var result = rows.Select(r => r.Something); new OutEnvelope<IEnumerable<string>>(result); // or just: return result; } }
IHandleRequest wywołuje inny handler
public OutEnvelope<ImportantData> Handle(GetImporantData.Request request) { var employeeId = _dispatcher.Dispatch( new GetEmployeeId.Request(request.SomeId)).Body; var rows = _connection.Query<SomeRow>(@" SELECT [Something] FROM [Somewhere] WHERE EmployeeId = @EmployeeId", new { EmployeeId = employeeId }); (...) }
IHandleRequest dekorujemy
public CacheHandler(IHandleRequest<TRequest, TResponse> wrapped, ICacheRequirement requirement, ICacheService<TRequest, OutEnvelope<TResponse>> cacheService) { _wrapped = wrapped; _requirement = requirement; _cacheService = cacheService; } public OutEnvelope<TResponse> Handle(TRequest request) { return _cacheService.GetOrAdd(_requirement, request, () => _wrapped.Handle(request)); }
IHandleRequest więcej dekorujemy!
public CacheHandler(...) public AuditHandler(...) public TracingHandler(...) public GlimpseHandler(...)
Dekorujemy ze StructureMap
public class GlimpsePolicy : IInterceptorPolicy { public string Description { get { return "..."; } } public IEnumerable<IInterceptor> DetermineInterceptors( Type type, Instance instance) { if (!type.IsHandlerInterfaceType()) { yield break; } var handler = typeof(GlimpseHandlerDecorator<,>) .MakeGenericType(type.GetGenericArguments()); yield return new DecoratorInterceptor(type, handler); } }
Testujemy cały handler
[Test] public void Some_Test_ThenError() { Given( // tworzymy Fakes wszystkich wywołań handlerów w testowanym handlerze new Fakes.UserInAgency(AgencyId, EmployeeId), new Fakes.HasPermission(EmployeeId, OrgId, ...), ...etc... ); When(new SearchCustomer.Request(...)); // request dla testowanego handlera Then( // oczekujemy rezultatu handlera new ValidationError("Document.Number", "Number missing")); }
WebApi - Controller
public class CustomerController : ApiController { [Route("api/customer/{CustomerId}")] [ResponseType(typeof (Customer))] public HttpResponseMessage Get([FromBodyAndUri] GetCustomer.Request request) { ...call dispatcher... } }
WebApi - BaseController
public class BaseController : ApiContoller { protected HttpResponseMessage Handle(object request) { if (!ModelState.IsValid) { ... } var dependencies = Request.GetDependencyScope(); var service = (IDispatcher)dependencies .GetService(typeof (IDispatcher)); var outEnvelope = (IOutEnvelope) service.Dispatch(request); return Request.CreateResponse(outEnvelope.Code, new ResponseBody(outEnvelope)); } }
Mono.Cecil & Fody for the win!
public void Execute() { var baseController = ModuleDefinition .GetTypes().Single(t => t.Name == "BaseController"); var handle = baseController .Resolve().Methods.Single(m => m.Name == "Handle"); var exampleController = baseController.Resolve().NestedTypes[0]; var exampleAction = exampleController.Methods[0]; var requests = GetRequests(ModuleDefinition).ToArray(); var types = new Dictionary<string, TypeDefinition>(); foreach (var request in requests) { TypeDefinition controller; if (types.TryGetValue(request.Area, out controller) == false) { ... } AddAction(request, controller, exampleAction, handle); } }
IHandleRequest - route attribute
[Authorize] [Route("Customer", "api/customer", RouteAttribute.Methods.Post)] public OutEnvelope<EmptyResponse> Handle(SaveCustomer.Request request) { (...) return OutEnvelope<EmptyResponse>.Ok(); }
Mono.Cecil - wygenerowane API Controllers
Fody - co jeszcze?
Feature Toggling - IFeature
public interface IFeature<TContext> { bool IsEnabled(); }
Feature Toggling - IToggler
public interface IToggler<TFeature, TContext> where TFeature : IFeature<TContext> { TFeature Get(TContext context); IEnumerable<TFeature> GetAll(TContext context); }
Feature Toggling - FeatureFinder
public interface IFeatureFinder { IEnumerable<TFeature> GetInstances<TFeature, TContext>(TContext context) where TFeature : IFeature<TContext>; TFeature GetDefault<TFeature, TContext>(TContext context) where TFeature : IFeature<TContext>; }
Implementacja Finder opiera się na przeszukiwaniu kontenera SM
[Cache("dbo", "SomeTable", "SomeColumnOne", "SomeColumn2")] public OutEnvelope<GetDesksForAgency.Response> Handle(GetDesksForAgency.Request request) { // ... }
public interface IId : IEquatable<IId> { }
public class DocumentId : Id.IntId<DocumentId> { }
[TypeConverter(typeof(DeskIdConverter))] public class DeskId : Id.IId { /// Format: {OrganizationId},{AgencyId},{DeskId} public static DeskId From(string value) { ... } public static bool TryFrom(string value, out DeskId id) { ... } public class DeskIdConverter : Id.BaseFromStringTypeConverter<DeskId> { protected override bool TryParse(string value, out DeskId id) { return TryFrom(value, out id); } } }
[PushToClientSide] public class TransactionDetailResponse { public TransactionDetailVisibility ColumnVisibility { get; private set; } public IEnumerable<TransactionDetail> Details { get; private set; } public TransactionDetailResponse(TransactionDetailVisibility columnVisibility, IEnumerable<TransactionDetail> details) { ... } }
export module ItWorks.Api.History.GetTransactionDetail { export class TransactionDetailResponse { ColumnVisibility: ItWorks.Api.History.GetTransactionDetail.TransactionDetailVisibility; Details: ItWorks.Api.History.GetTransactionDetail.TransactionDetail[]; } export class TransactionDetail { TransactionType: ItWorks.Api.Transaction.TransactionTypes; Amounts: ItWorks.Api.History.GetTransactionDetail.Amounts; ... } ... }
JetBrains Upsource
...a dlaczego nie Atlassian Crucible?
...dlatego