Lab5 Maykon Bergamaschi

aluno: Maykon Bergamaschi
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 07/12/2008 (2a exames)

Introdução

O objetivo deste laboratório é a implementação de melhorias de um backend de uma aplicação em Ruby responsável por gerenciar as requisições de usuários de um "Bolão", sistema de apostas em resultados de jogos de futebol.
A atividade inclui conceitos importantes vistos em sala de aula, como os padrões de projeto Builder e Decorator.

Desenvolvimento

Decorator Pattern

Melhoria nos Decorators de Thread Safety

A atividade começou com a implementação dos decorators de Thread Safety. Por se tratar de uma aplicação que pode ser acessada por vários usuários simultaneamente, devem ser evitadas condições de corrida, como dois usuários realizando apostas em um mesmo jogo ao mesmo tempo.

Para implementar essa nova característica à aplicação, foram criadas duas novas classes que decoram outras duas já existentes: MatchThreadSafeDecorator, que decora Match; e MatchListThreadSafeDecorator, que, por sua vez, decora MatchList. Elas foram construídas dessa forma uma vez que havia necessidade de decorar os jogos um a um.

A seguir, para exemplificar o que foi feito, o código de MatchThreadSafeDecorator:

require 'monitor'
module Decorators
 
class MatchThreadSafeDecorator < Decorator
 
  include MonitorMixin
 
  def make_bet(score, user)
      synchronize do
        @decorated.make_bet(score, user)
      end
  end    
 
  def finalize(final_score)
      synchronize do
        @decorated.finalize(final_score)
      end   
  end
end
 
end

Melhoria nos Decorators de Persistência

A aplicação deve armazenar os dados de jogos e apostas em um pequeno banco no disco. Para a implementação dessa propriedade, foram criadas duas classes, de modo análogo à melhoria nos Decorators de Thread Safety, MatchPersistanceDecorator, que decora Match, e MatchListPersistanceDecorator, que decora MatchList.

Para exemplificar, o código de MatchPersistanceDecorator:

require 'monitor'
module Decorators
 
class MatchPersistanceDecorator < Decorator
 
  include MonitorMixin
 
   def make_bet(*args)
     bet = @decorated.make_bet(*args)
     @match_list_persistance_obj.save
     bet
   end
 
  def finalize(final_score)
     fin = @decorated.finalize(final_score) 
     @match_list_persistance_obj.save
     fin
  end
 
  def initialize(obj, match_list)
    super(obj)
    @match_list_persistance_obj = match_list
  end
 
end
 
end

Builder Pattern

Para facilitar a construção de objetos dessas classes decoradas, foram criadas duas classes construtoras seguindo o padrão Builder, MatchListBuilder e UserListBuilder. Elas possuem um método chamado add_all, que decora o objeto com persistência, Thread Safety e Logger - na verdade, add_all é uma chamada de três outros métodos, cada um deles decora o objeto com alguma das características mencionadas.

As classes construtoras geradas têm métodos que retornam objetos decorados, como se vê a seguir, no código de MatchListBuilder. Observe também que o construtor de MatchListBuilder possui uma chamada para o método load_list, para carregar os dados a partir do disco.

require 'match_list'
include Decorators
 
  class MatchListBuilder
 
    attr_reader :matchlist
 
    def initialize
      @matchlist = MatchList.new()
      @matchlist.load_list if @added_persistance
      @matchlist
    end
 
    def add_all
      add_persistance
      add_logging
      add_thread_safety 
    end
 
    def add_persistance
      @matchlist = MatchListPersistanceDecorator.new(@matchlist, $match_db_name)
    end
 
    def add_logging
      @matchlist = MatchListLoggerDecorator.new(@matchlist)
    end
 
    def add_thread_safety
      @matchlist = MatchListThreadSafeDecorator.new(@matchlist)
    end
 
  end

Processamento de requisições

Para codificar as requisições e as saídas do programa, foi utilizada a YAML, formato para serializar dados para que possam ser lidos por humanos. Foi criado um módulo chamado Readers, que contém as classes responsáveis por tratar as requisições.

A classe RequestReader lê a partir do input em YAML o que está sendo requisitado e, com base no pedido feito, chama o construtor de uma das classes de requisição. Nessa operação, é passado um HashMap com as informações referentes ao pedido.

A seguir, o código da classe RequestReader:

require 'yaml'
 
include Readers
    class RequestReader
 
    def initialize (match_list=$match_db, user_list=$user_db)
      @match_db = match_list
      @user_db = user_list 
 
    end   
 
    def read(io) 
      #lê a entrada linha a linha até acharmos um "INICIO"     
 
      while (!io.eof?)
 
      loop do
        string = io.gets
        break if string && string.include?("INICIO")
      end
      yaml = io.gets("\nFIM") #depois do INICIO, lê tudo até a palavra FIM
      yaml = yaml.chomp("FIM") #tira o FIM do final do string que foi lido do io
      request = YAML.load(yaml) # aqui já temos o hash com a requisicao processada
 
    #processa requisicão e escreve saída
    case request['request']['operation']
 
        when "creation"
          req = RequestCreation.new(request)
 
        when "authentication"
          req = RequestAuthentication.new(request)
 
        when "match_listing"
          req = RequestMatchListing.new(request)
 
        when "finished_match_listing"
          req = RequestFinishedMatchListing.new(request)
 
        when "match_detailed_info"
          req = RequestMatchDetailedInfo.new(request)
 
        when "my_bet_info"
          req = RequestMyBetInfo.new(request)
 
        when "make_bet"
          req = RequestMakeBet.new(request)
 
        when "connection_end"
          req = RequestConnectionEnd.new(request)
 
        else 
          $file_output.puts("Erro no arquivo de input. Operacao inexistente.")
        end
 
    end   
 
    end
 
  end

Para ilustrar essas classes de requisição, será mostrada a classe responsável por fazer uma aposta em uma partida. Ela é chamada RequestMakeBet e tem seu código mostrado a seguir. Assim como todas as classes de requisição, no seu construtor é feita a chamada de um construtor de uma classe do módulo Printers, para gerar o output em formato YAML - no caso de RequestMakeBet, essa classe é YamlBetCreationPrinter.

#"make_bet"
  class RequestMakeBet
    def initialize(request)
      @username = request['request']['parameters'][0] #username
      @match_name = request['request']['parameters'][1] #match_name
      @score = request['request']['parameters'][2].to_score #score
 
      @match = $match_list.find_by_name(@match_name)
      @user = $user_list.find_by_name(@username)
      @user.make_bet(@score, @match) if (!!@match && !!@user) 
      YamlBetCreationPrinter.new($match_list,@match,@user,@score,$file_output).print_bet_creation
    end
  end

Esta classe é aquela cujo construtor é chamado pelo construtor de RequestMakeBet, YamlBetCreationPrinter. Ela gera a saída em formato YAML no arquivo indicado por io:

require 'yaml'
module Printers
  class YamlBetCreationPrinter
    def initialize(matchlist, match, user, score, io = $stdout)     
      @matchlist, @match, @user, @score, @io = matchlist, match, user, score, io
    end
 
    def print_bet_creation
 
      @io.puts("INICIO")
 
        if (!@user)
        @io.puts(
            {
              :response => "failure"
            }.to_yaml
        )
        else if (!@user.authenticated?)
              @io.puts(
                    {
                      :response => "failure"
                    }.to_yaml
                  ) 
          else if (!@match)
              @io.puts(
                    {
                      :response => "failure"
                    }.to_yaml
                  )            
             else
              @io.puts(
                  {
                    :response => "success",
                    :user_name => @user.username,
                    :match_name => @match.name,
                    :score => @score
                  }.to_yaml
 
              )
 
             end
          end
        end  
      @io.puts("FIM")
 
    end
  end
end

Testes (sem o servidor)

Foram realizados alguns testes "manuais" para testar o bom funcionamento da aplicação. Primeiramente, foram realizados os testes unitários.

Como pode se ver a seguir, os resultados desses testes mostram o funcionamento correto.

teste_01.JPG
Figura 1 - Resultado dos testes unitários

Foram feitos também testes com arquivos de input em YAML. Para a execução deles, foram inseridas algumas linhas no método main, para que fosse feita a leitura a partir de mais arquivos. Entre a leitura, foi disparado um evento de finalizar as apostas de uma partida - tal evento não pode ser feito por usuários do Bolão, isso deve ser controlado pelo administrador.

O conteúdo dos arquivos de input são:

input1.yml

INICIO
---
request:  
 operation: creation  
 parameters:  
  - Joao  
  - 123456  
FIM
INICIO
---
request:  
 operation: authentication  
 parameters:  
  - Jose  
  - 65432  
FIM
INICIO
---
request:  
 operation: creation  
 parameters:  
  - Jose  
  - 654321  
FIM
INICIO
---
request:  
 operation: authentication  
 parameters:  
  - Joao  
  - 123456  
FIM
INICIO
---
request:  
 operation: authentication  
 parameters:  
  - Jose  
  - 654321  
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:
  - Jose
  - Fla x Vasco
  - 0 x 2
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:
  - Joao
  - Fla x Vasco
  - 0 x 2
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:
  - Joao
  - Flu x Vasco
  - 3 x 4
FIM
INICIO
---
request:  
 operation: connection_end
 parameters:
  - Joao  
FIM
INICIO
---
request:  
 operation: match_detailed_info
 parameters:
  - Flu x Vasco 
FIM
INICIO
---
request:  
 operation: my_bet_info
 parameters:
  - Joao  
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:
  - Joao
  - Flu x Vasco
  - 3 x 4
FIM

Para encerrar a partida, a seguinte linha no método main:

$match_list.find_by_name("Fla x Vasco").finalize("0 x 2")

input2.yml

INICIO
---
request:  
 operation: authentication  
 parameters:  
  - Joao  
  - 123456  
FIM
INICIO
---
request:  
 operation: match_listing  
FIM
INICIO
---
request:  
 operation: match_detailed_info
 parameters:
  - Fla x Vasco 
FIM
INICIO
---
request:  
 operation: my_bet_info
 parameters:
  - Joao  
FIM
INICIO
---
request:  
 operation: my_bet_info
 parameters:
  - Joao1  
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:
  - Joao
  - Flu x Vasco
  - 3 x 4
FIM
INICIO
---
request:  
 operation: connection_end
 parameters:
  - Jose  
FIM
INICIO
---
request:  
 operation: connection_end
 parameters:
  - Joao  
FIM
INICIO
---
request:  
 operation: match_detailed_info
 parameters:
  - Flu x Vasco 
FIM
INICIO
---
request:  
 operation: match_detailed_info
 parameters:
  - Fla x Vasco 
FIM

A saída do programa é o arquivo output1.yml:

INICIO
--- 
:response: success
:username: Joao
:password: 123456
FIM
INICIO
--- 
:response: failure
:output_message: Jose not logged
FIM
INICIO
--- 
:response: success
:username: Jose
:password: 654321
FIM
INICIO
--- 
:response: success
:output_message: Joao logged
FIM
INICIO
--- 
:response: success
:output_message: Jose logged
FIM
INICIO
--- 
:match_name: Fla x Vasco
:response: success
:user_name: Jose
:score: "0x2"
FIM
INICIO
--- 
:match_name: Fla x Vasco
:response: success
:user_name: Joao
:score: "0x2"
FIM
INICIO
--- 
:match_name: Flu x Vasco
:response: success
:user_name: Joao
:score: 3x4
FIM
INICIO
--- 
:response: success
:username: Joao
FIM
INICIO
--- 
:bet_count: 1 bets
:winners: no winners
:response: success
:final_score: _ x _
:name: Flu x Vasco
:finalized: not finalized
:end_time: 15/12/2008 22:00
FIM
INICIO
--- 
:response: failure
:output_message: user not logged
FIM
INICIO
--- 
:response: failure
FIM
INICIO
--- 
:response: success
:output_message: Joao logged
FIM
INICIO
--- 
:response: 
- :bet_count: 1 bets
  :name: Flu x Vasco
  :end_time: 15/12/2008 22:00
FIM
INICIO
--- 
:bet_count: 2 bets
:winners: Jose, Joao
:response: success
:final_score: "0x2"
:name: Fla x Vasco
:finalized: finalized
:end_time: 10/12/2008 16:00
FIM
INICIO
current bets
--- 
:response: 
- :match_name: Flu x Vasco
finalized bets
--- 
:response: 
- :match_name: Fla x Vasco
winning bets
--- 
:response: 
- :match_name: Fla x Vasco
FIM
INICIO
--- 
:response: failure
:output_message: user not found
FIM
INICIO
--- 
:match_name: Flu x Vasco
:response: success
:user_name: Joao
:score: 3x4
FIM
INICIO
--- 
:response: success
:username: Jose
FIM
INICIO
--- 
:response: success
:username: Joao
FIM
INICIO
--- 
:bet_count: 1 bets
:winners: no winners
:response: success
:final_score: _ x _
:name: Flu x Vasco
:finalized: not finalized
:end_time: 15/12/2008 22:00
FIM
INICIO
--- 
:bet_count: 2 bets
:winners: Jose, Joao
:response: success
:final_score: "0x2"
:name: Fla x Vasco
:finalized: finalized
:end_time: 10/12/2008 16:00
FIM

Servidor

A implementação do servidor multithread foi feita no arquivo server.rb, por meio do seguinte código (includes foram omitidos):

def server
 
    $server = TCPServer.new(5000)  # porta utilizada = 5000
    while ( io_socket = $server.accept ) 
# aguarda até que conexão seja recebida, 
# retornando um TCPSocket com a conexão.
      Thread.new do
      # dispare uma nova thread
# e utilize io_socket para efetuar
# a entrada e saída do método main, 
# de dentro desta thread
      io_socket.puts "Conectado"
# io_socket deve ser utilizado tanto
# para saída quanto para entrada, portanto
# é passado como parâmetro in e
# parâmetro out de main.
      go_to_main(io_socket,io_socket)
      io_socket.puts "Encerrando conexao..."
      io_socket.close
      io_socket.puts "Desconectado" 
      end
    end
end
 
def go_to_main(in_socket, out_socket)
  main(in_socket, out_socket)
  request_reader = RequestReader.new($match_list, $user_list)
  while (!request_reader.is_a?(RequestConnectionEnd))
    request_reader.read(in_socket, out_socket)
  end
end

Foi feito um pequeno teste, com dois usuários fazendo apostas simultaneamente no mesmo jogo. O resultado, apesar de o formato de saída não facilitar muito a leitura, comprova o bom funcionamento do servidor.

teste_02.JPG
Figura 2: teste com o servidor multithread

O servidor poderia receber algumas melhorias, como o formato de saída dos dados ou uma forma de leitura melhor que a atual - inserção via teclado do formato YAML, de modo que qualquer erro de digitação não pode ser apagado, a requisição deve ser totalmente refeita.

Conclusão

Esta atividade foi bastante interessante, pois os alunos tiveram que partir de (e compreender) linhas de código em Ruby para gerar a aplicação especificada. O aprendizado propiciado foi grande com a realização deste laboratório.

Indubitavelmente foi uma tarefa muito trabalhosa, mesmo ela valendo por duas. Foram muitas horas de trabalho em um período especialmente complicado, as semanas do fim do 2o. bimestre e de exames.

Observação sobre a aplicação: os arquivos de input devem ser colocados em "c:\". Isso pode ser alterado em main.rb.

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License