Event

Description

The event mechanism is used to inform objects and/or services, called event listeners, that something has happened in another object or service, called event source, so that appropriate actions can be taken in the event listeners. The information about 'something has happened' is contained in an object, called event data, or simply event. The event source's act of informing event listeners about an event is called generating an event (also called firing an event). The action taken in an event listener is defined through a so called event handler

A real life example would be a news publisher that sends a message to all subscribers, whenever something important has happened. In this example, the event source is the news publisher, the event listeners are the subscribers, and the event data is the message that is send from the publisher to all the subscribers

Each object and each service can generate 0, one or more different types of events. The list of all event types that are generated must be declared in the event source. At runtime, 0 1 or more event listeners can exist for each event type in an event source. Each event listener must define one event handler for each event it listens to. One event handler can be used to handle several types of events coming from different event sources, as long as the types of all those events are compatible to the type of event that is handled by the event handler. For example, one event handler could listen to left and right mouse clicks coming from buttons and text input fields.

The following figure shows possible interactions:

Figure 6.2. Events

Events

There are different instructions used to declare, generate, and handle events.

Event declaration instruction

Each event that can be generated from an object or service must be declared. Events generated from an object are declared in the object's type and events generated from a service are declared in the service.

Each event is identified by a unique event identifier. An event identifier is a prefixed identifier starting with ev_ as prefix. For more information about prefixed identifiers see the section called “Prefixed identifier”.

The type of the event data must also be declared and it must be a child type of:

  • li_obix.li_events.ty_object_event if the event source is an object
  • li_obix.li_events.ty_service_event if the event source is a service.

Both above types inherit from li_obix.li_events.ty_event, which is the root type of all events.

The syntax to declare an event is:

ProductionSyntaxLinks
event

"event" ( "id" ":" ) ? event_id event_properties "end"

the section called “Event”

example:

event coffee_ready type:coffee_ready_event end

Event generation instruction

An event is generated with the generate event instruction which is defined as:

ProductionSyntaxLinks
generate_event_instruction"generate" "event" event_id "from" expressionthe section called “generate event instruction”

expression defines the event object (event data) that will be generated

example:

generate event coffee_ready from fa_coffee_ready_event.co_create ( kind = "Espresso" )

For more information see: the section called “generate event instruction”

Start event listening instruction

To start listening for an event (i.e. to 'register as a listener' or to 'subscribe to an event'), the following syntax is used:

ProductionSyntaxLinks
on_event_instruction"on" "event" event_id_list ( "in" event_source ) ? "execute" event_handler ( "handler" ":" variable_id ) ?the section called “on event instruction”

example:

on event coffee_ready in coffee_machine execute drink_coffee

For more information see: the section called “on event instruction”

Stop event listening instruction

To stop listening for an event (i.e. to 'unregister as a listener' or to 'unsubscribe from an event'), the following syntax is used:

ProductionSyntaxLinks
stop_event_handler_instruction"stop" "event" "handler" expressionthe section called “stop event handler instruction”

expression must evaluate to the event handler object that has been created with the on event instruction.

example:

stop event handler drink_coffee_event_handler

For more information see: the section called “stop event handler instruction”

Example

Example 6.10. Events in a television delivery stack

[Note]Note
The following example uses features not covered yet (e.g. type inheritance and generic types). No need to worry if some details are not yet explained; they can be considered as a sneak preview of techniques explained in subsequent chapters.

Suppose we have a first-in-first-out stack of televisions to be delivered. A new television to be delivered can be added to the stack with a push command, and the least recently added television can be removed from the stack for delivery with a pop command.

First we have to define type television:

type television default_factory:yes
   attribute model type:string end
   attribute customer type:string end
   // other attributes omitted for brevity
end

Type television_stack can be written as:

type television_stack_1

   attribute is_empty type:yes_no kind:readonly_variable end

   command push
      in television type:television end
   end

   command pop
      in_check check: not i_object_.a_is_empty end

      out television type:television end
   end

end type

Now suppose that other objects or services want to be informed whenever a television is pushed or popped. For example, it might be necessary to log each action in a database, or to send an email whenever a television is removed from the stack for delivery. This can be done with events.

First we have to define two types for the two events, television_pushed and television_popped. We assume that both events hold the same simple data: the television that has been pushed or popped, and the time the event occurred. Hence, we first define a parent type that inherits from type object_event:

type television_pushed_or_popped_event

   inherit object_event end

   attribute television type:television end
   attribute time type:date_time end
end
[Note]Note

Type object_event in Obix's standard library is defined as:

type object_event

	inherit event end

end type

And type event in Obix's standard library is defined as:

type event

	attribute source type:any_type end

	// attribute time type:date_time voidable:yes end

end type

The two event types can now be written using type inheritance again:

type television_pushed_event default_factory:yes
   inherit television_pushed_or_popped_event end
end
type television_popped_event default_factory:yes
   inherit television_pushed_or_popped_event end
end

Both events are generated from objects of type television_stack, so we have to declare that by adding two event declaration instructions at the end of type television_stack:

type television_stack

   attribute is_empty type:yes_no kind:readonly_variable end

   command push
      in television type:television end
   end

   command pop
      in_check check: not i_object_.a_is_empty end

      out television type:television end
   end
   
   event television_pushed type: television_pushed_event end
   event television_popped type: television_popped_event end

end type

What remains to be done for our event source is to write an implementation for type television_stack. A possible solution would be:

factory television_stack type: television_stack

   // this private attribute contains the list of all pending televisions in the stack
   attribute television_list type:!mutable_indexed_list<television> default: !mutable_indexed_list_factory<television>.co_create private:yes end

   attribute is_empty
      get
         script
            // simply return attribute 'a_is_empty' of private attribute 'a_television_list'
            o_is_empty = a_television_list.a_is_empty
         end
      end
   end

   command push
      script

         // append new television to the list of pending televisions
         a_television_list.co_append ( i_television )

         // generate a 'television_pushed' event
         generate event television_pushed from fa_television_pushed_event.co_create ( &
            source = this &
            television = i_television &
            time = se_date_time.a_current_date_time )
      end
   end

   command pop
      script

         // take least recently television from the list of pending televisions
         o_television = a_television_list.co_first_item
         a_television_list.co_remove_first

         // generate a 'television_popped' event
         generate event television_popped from fa_television_popped_event.co_create ( &
            source = this &
            television = o_television &
            time = se_date_time.a_current_date_time )
      end
   end

   creator create kind:in_all end

end factory

That's it for the event source. Let's now create an event listener. The following service listens to events in a television_stack object. To keep the example simple we will just display a message on the system console, each time an event is generated.

service television_stack_logger

   command start_logging
      in television_stack type: television_stack end
      script
         // register two event handlers for input argument 'television_stack'

         // each time a 'television_pushed' event is fired in 'i_television_stack', execute command 'new_pushed_event'
         on event television_pushed in i_television_stack execute script new_pushed_event

         // each time a 'television_popped' event is fired in 'i_television_stack', execute command 'new_popped_event'
         on event television_popped in i_television_stack execute script new_popped_event
      end
   end
   
   command_list private:yes // all following commands are private

      command new_pushed_event
         in event type: television_pushed_event end

         script
             co_log_event ( &
                television = i_event.a_television &
                action = "pushed" &
                time = i_event.a_time )
         end
       end

       command new_popped_event
          in event type: television_popped_event end

          script
             co_log_event ( &
                television = i_event.a_television &
                action = "popped" &
                time = i_event.a_time )
          end
       end

       command log_event
          in television type: television end
          in action type: string end
          in time type: date_time end

          script
             se_console.co_message ( "Television " & i_television.a_model & &
                " of customer " & i_television.a_customer & " has been " & i_action & &
                " on " & i_time.co_to_string )
          end
       end

   end command_list
end service

We are now ready to use our tiny application. Let's create a television_stack, ask service television_stack_logger to listen for events in television_stack, push and pop some televisions and see what is displayed on the system console:

service television_stack_test

   command test
      script
         // create a 'television_stack'
         var television_stack television_stack = fa_television_stack.co_create
         
         // ask 'se_television_stack_logger' to log events
         se_television_stack_logger.co_start_logging ( v_television_stack )
         
         // push a new television to be delivered
         v_television_stack.co_push ( fa_television.co_create ( &
              model = "Nice telli" &
              customer = "Nicolas Paganini" ) )
              
         // wait 5 seconds
         se_time.co_wait_seconds ( 5 )

         // push a second television
         v_television_stack.co_push ( fa_television.co_create ( &
              model = "ABC 123" &
              customer = "Wolfgang" ) )
              
         // wait 7 seconds
         se_time.co_wait_seconds ( 7 )
         
         // pop television
         var television popped_television = v_television_stack.co_pop
      end
   end
end

Executing the above command will display the following on the system console:

Television Nice telli of customer Nicolas Paganini has been pushed on Aug 10, 2007 1:39:22 PM
Television ABC 123 of customer Wolfgang has been pushed on Aug 10, 2007 1:39:27PM
Television Nice telli of customer Nicolas Paganini has been popped on Aug 10, 2007 1:39:34 PM
[Note]Note

It is of course legitimate to ask why the same result couldn't be achieved by simply displaying a message in commands push and pop of factory television_stack, instead of using events and writing the whole code to manage them. Well, the same result could indeed be achieved, but the very big advantage of using events is that responsibilities (e.g. the responsibility to log actions) can much better be modularized and distributed, which sustains separation of concerns, gives us much more flexibility at runtime, and results in code that is easier to maintain.

For example, suppose that besides logging each push and pop action, we are required to send an email notification each time a new television is popped. In that case we can simply create another service which takes care of sending the emails. The great advantage is that we can do this without changing anything in the existing code. The email service would just be another listener to events in television_stack. Moreover, this new service could also be used to listen to other events in other objects and send appropriate emails. As a result, the whole job of sending emails throughout the whole application is assured by one single software component with a well defined responsibility. Thus, in case of any changes regarding email notification, only one software component needs to be adapted.