Skip to content

Latest commit

 

History

History
106 lines (75 loc) · 5.53 KB

File metadata and controls

106 lines (75 loc) · 5.53 KB

Proof of concept

Given intentions-as-code of a simplest web page, generate its front-end and backend implementations.

  • Intentions as code: C# 7 on .NET Core 2
  • Front-end: ES6 / JSX, a Hyperapp application
  • Backend: C#, an ASP.NET Core 2 application

Web page flow

  1. On the web page, user enters her name and clicks "Submit" button.
  2. The name is sent to GetGreetingForName operation in the backend
  3. The backend invokes Greeting service, which calculates greeting text for specified name.
  4. Resulting greeting is sent back to the web page
  5. Web page receives the response and displays the greeting.

Intentions as code

Here we use an imaginary domain-specific API (a kind of domain-specific language), which allows describing a web app in code.

Each page in web app is a class derived from WebPage<TModel>, where TModel is the model (or the state) of the page. The only page in our POC is HelloPage:

public class HelloPage : WebPage<HelloPage.HelloModel>

A page contains one or more components. Some components are built-in, while others are custom (although the latter are not part of the POC).

A component is included in a page by declaring read-only property like this:

public FormComponent<HelloModel> Form => new FormComponent<HelloModel> {
    Model = Model
};

What have just happened:

  1. We included a built-in component FormComponent<TModel> in our page
  2. We named it Form (names within one page are unique)
  3. We stated that it is bound to HelloModel (similarly to pages, every components is bound to a model).
  4. We bound component's Model property to page's Model, which means that Form is bound to the model of the entire page.

Models are classes enriched with semantics metadata. We explain semantics through C# attributes. The HelloModel class looks like this:

public class HelloModel
{
    [PropertyContract.Validation.Required(AllowEmpty = false, MaxLength = 50)]
    public string Name { get; set; }

    [PropertyContract.I18n.Label("ServerSays")]
    public string Greeting { get; set; }
}

Now that we have a form and a model, and want to send data to server when the form is submitted. For that, we need to describe our backend API. We do it with a read-only property like this:

public IGreetingService GreetingService => GetBackendApiProxy<IGreetingService>();

Function GetBackendApiProxy<T> is a marker API that explains our meta-programming logic that GreetingService property represents backend API. IGreetingService is an application-defined interface in our example:

public interface IGreetingService
{
    Task<string> GetGreetingForName(string name);
}

The last piece that's left to add is an intention to invoke Greeting service, pass it the Name from the model, and assign resulting greeting to the model. Here's how:

public override void Controller()
{
    Form.Submitting += async () => Model.Greeting = await GreetingService.GetGreetingForName(Model.Name);
}

The Controller method is where we describe interactions between components, the page, and the backend. Here we attach handler to Submitting event of the Form component. As the name implies, this event is fired when the user submits the form.

That's all. The full code listing is in HelloPage.cs

Packages

The following packages are part of the test

Proof-of-concept packages

Production packages

Integration test